Merge pull request #9 from NewLifeX/copilot/analyze-milestones-m0-to-m13
Stone authored at 2026-02-19 07:32:56 GitHub committed at 2026-02-19 07:32:56
20.82 KiB
NewLife.NovaDb
using System;
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using NewLife.NovaDb.Client;
using NewLife.NovaDb.Server;
using Xunit;

namespace XUnitTest;

/// <summary>集成测试服务器夹具,在所有集成测试之间共享同一个 NovaServer 实例</summary>
public class IntegrationServerFixture : IDisposable
{
    /// <summary>NovaDb 服务器实例</summary>
    public NovaServer Server { get; }

    /// <summary>服务器端口</summary>
    public Int32 Port { get; }

    /// <summary>数据库路径</summary>
    public String DbPath { get; }

    public IntegrationServerFixture()
    {
        DbPath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), $"NovaIntegration_{Guid.NewGuid():N}");
        Server = new NovaServer(0) { DbPath = DbPath };
        Server.Start();
        Port = Server.Port;
    }

    public void Dispose()
    {
        Server.Dispose();

        if (!String.IsNullOrEmpty(DbPath) && System.IO.Directory.Exists(DbPath))
        {
            try { System.IO.Directory.Delete(DbPath, recursive: true); }
            catch { }
        }
    }
}

/// <summary>NovaDb 集成大测试,启动 NovaServer 后通过客户端完成完整数据库操作</summary>
[Collection("IntegrationTests")]
public class IntegrationTests : IClassFixture<IntegrationServerFixture>
{
    private readonly IntegrationServerFixture _fixture;
    private Int32 _port => _fixture.Port;

    public IntegrationTests(IntegrationServerFixture fixture)
    {
        _fixture = fixture;
    }

    #region 连接测试

    [Fact(DisplayName = "集成测试-服务器启动与客户端连接")]
    public async Task TestServerStartAndClientConnect()
    {
        Assert.True(_fixture.Server.IsRunning);
        Assert.True(_port > 0);

        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();
        Assert.True(client.IsConnected);

        var result = await client.PingAsync();
        Assert.NotNull(result);
        Assert.NotEmpty(result);

        client.Close();
        Assert.False(client.IsConnected);
    }

    [Fact(DisplayName = "集成测试-ADO.NET连接")]
    public void TestAdoNetConnection()
    {
        using var conn = new NovaConnection
        {
            ConnectionString = $"Server=127.0.0.1;Port={_port}"
        };

        Assert.Equal(ConnectionState.Closed, conn.State);
        Assert.False(conn.IsEmbedded);

        conn.Open();
        Assert.Equal(ConnectionState.Open, conn.State);
        Assert.NotNull(conn.Client);
        Assert.True(conn.Client!.IsConnected);

        conn.Close();
        Assert.Equal(ConnectionState.Closed, conn.State);
    }

    #endregion

    #region 创建表测试

    [Fact(DisplayName = "集成测试-RPC创建表")]
    public async Task TestRpcCreateTable()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        var rows = await client.ExecuteAsync("CREATE TABLE t_rpc_create (id INT PRIMARY KEY, name VARCHAR NOT NULL, age INT)");
        Assert.Equal(0, rows);

        // 再次创建 IF NOT EXISTS 不报错
        rows = await client.ExecuteAsync("CREATE TABLE IF NOT EXISTS t_rpc_create (id INT PRIMARY KEY, name VARCHAR)");
        Assert.Equal(0, rows);
    }

    [Fact(DisplayName = "集成测试-ADO.NET创建表")]
    public void TestAdoNetCreateTable()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();

        using var cmd = conn.CreateCommand();
        cmd.CommandText = "CREATE TABLE t_ado_create (id INT PRIMARY KEY, name VARCHAR NOT NULL, score DOUBLE)";
        var rows = cmd.ExecuteNonQuery();
        Assert.Equal(0, rows);
    }

    #endregion

    #region 添删改查测试

    [Fact(DisplayName = "集成测试-RPC添删改查")]
    public async Task TestRpcCrud()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        // 创建表
        await client.ExecuteAsync("CREATE TABLE t_rpc_crud (id INT PRIMARY KEY, name VARCHAR NOT NULL, age INT)");

        // INSERT
        var rows = await client.ExecuteAsync("INSERT INTO t_rpc_crud (id, name, age) VALUES (1, 'Alice', 25)");
        Assert.Equal(1, rows);

        rows = await client.ExecuteAsync("INSERT INTO t_rpc_crud VALUES (2, 'Bob', 30)");
        Assert.Equal(1, rows);

        // SELECT - 通过 Query 验证
        var result = await client.QueryAsync<IDictionary<String, Object>>("SELECT * FROM t_rpc_crud");
        Assert.NotNull(result);

        // UPDATE
        rows = await client.ExecuteAsync("UPDATE t_rpc_crud SET name = 'Alice Smith', age = 26 WHERE id = 1");
        Assert.Equal(1, rows);

        // 验证 UPDATE 结果
        result = await client.QueryAsync<IDictionary<String, Object>>("SELECT name, age FROM t_rpc_crud WHERE id = 1");
        Assert.NotNull(result);

        // DELETE
        rows = await client.ExecuteAsync("DELETE FROM t_rpc_crud WHERE id = 2");
        Assert.Equal(1, rows);

        // 验证 DELETE 后只剩一条
        result = await client.QueryAsync<IDictionary<String, Object>>("SELECT COUNT(*) FROM t_rpc_crud");
        Assert.NotNull(result);
    }

    [Fact(DisplayName = "集成测试-ADO.NET添删改查")]
    public void TestAdoNetCrud()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();
        using var cmd = conn.CreateCommand();

        // 创建表
        cmd.CommandText = "CREATE TABLE t_ado_crud (id INT PRIMARY KEY, name VARCHAR NOT NULL, age INT)";
        cmd.ExecuteNonQuery();

        // INSERT
        cmd.CommandText = "INSERT INTO t_ado_crud (id, name, age) VALUES (1, 'Alice', 25)";
        var rows = cmd.ExecuteNonQuery();
        Assert.Equal(1, rows);

        cmd.CommandText = "INSERT INTO t_ado_crud VALUES (2, 'Bob', 30)";
        rows = cmd.ExecuteNonQuery();
        Assert.Equal(1, rows);

        cmd.CommandText = "INSERT INTO t_ado_crud VALUES (3, 'Charlie', 35)";
        rows = cmd.ExecuteNonQuery();
        Assert.Equal(1, rows);

        // UPDATE
        cmd.CommandText = "UPDATE t_ado_crud SET name = 'Alice Smith', age = 26 WHERE id = 1";
        rows = cmd.ExecuteNonQuery();
        Assert.Equal(1, rows);

        // DELETE
        cmd.CommandText = "DELETE FROM t_ado_crud WHERE id = 3";
        rows = cmd.ExecuteNonQuery();
        Assert.Equal(1, rows);
    }

    [Fact(DisplayName = "集成测试-ADO.NET查询DataReader")]
    public void TestAdoNetQueryDataReader()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();
        using var cmd = conn.CreateCommand();

        // 创建表并插入数据
        cmd.CommandText = "CREATE TABLE t_ado_reader (id INT PRIMARY KEY, name VARCHAR NOT NULL, age INT)";
        cmd.ExecuteNonQuery();

        cmd.CommandText = "INSERT INTO t_ado_reader VALUES (1, 'Alice', 25)";
        cmd.ExecuteNonQuery();
        cmd.CommandText = "INSERT INTO t_ado_reader VALUES (2, 'Bob', 30)";
        cmd.ExecuteNonQuery();

        // SELECT 查询
        cmd.CommandText = "SELECT * FROM t_ado_reader ORDER BY id ASC";
        using var reader = cmd.ExecuteReader();

        Assert.True(reader.HasRows);
        Assert.Equal(3, reader.FieldCount);

        Assert.True(reader.Read());
        Assert.Equal("Alice", reader.GetString(1));

        Assert.True(reader.Read());
        Assert.Equal("Bob", reader.GetString(1));

        Assert.False(reader.Read());
    }

    [Fact(DisplayName = "集成测试-ADO.NET ExecuteScalar")]
    public void TestAdoNetExecuteScalar()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();
        using var cmd = conn.CreateCommand();

        // 创建表并插入数据
        cmd.CommandText = "CREATE TABLE t_ado_scalar (id INT PRIMARY KEY, name VARCHAR)";
        cmd.ExecuteNonQuery();

        cmd.CommandText = "INSERT INTO t_ado_scalar VALUES (1, 'Alice')";
        cmd.ExecuteNonQuery();
        cmd.CommandText = "INSERT INTO t_ado_scalar VALUES (2, 'Bob')";
        cmd.ExecuteNonQuery();

        // COUNT 标量查询
        cmd.CommandText = "SELECT COUNT(*) FROM t_ado_scalar";
        var count = cmd.ExecuteScalar();
        Assert.NotNull(count);
    }

    #endregion

    #region 批量操作测试

    [Fact(DisplayName = "集成测试-RPC批量插入")]
    public async Task TestRpcBatchInsert()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        await client.ExecuteAsync("CREATE TABLE t_rpc_batch (id INT PRIMARY KEY, name VARCHAR NOT NULL, score INT)");

        // 批量插入多行
        var rows = await client.ExecuteAsync("INSERT INTO t_rpc_batch VALUES (1, 'Alice', 90), (2, 'Bob', 85), (3, 'Charlie', 92), (4, 'Diana', 88), (5, 'Eve', 95)");
        Assert.Equal(5, rows);
    }

    [Fact(DisplayName = "集成测试-ADO.NET批量插入")]
    public void TestAdoNetBatchInsert()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();
        using var cmd = conn.CreateCommand();

        cmd.CommandText = "CREATE TABLE t_ado_batch (id INT PRIMARY KEY, name VARCHAR NOT NULL, age INT)";
        cmd.ExecuteNonQuery();

        // 多行 INSERT
        cmd.CommandText = "INSERT INTO t_ado_batch VALUES (1, 'Alice', 25), (2, 'Bob', 30), (3, 'Charlie', 35), (4, 'Diana', 28), (5, 'Eve', 32)";
        var rows = cmd.ExecuteNonQuery();
        Assert.Equal(5, rows);
    }

    [Fact(DisplayName = "集成测试-RPC批量查询")]
    public async Task TestRpcBatchQuery()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        await client.ExecuteAsync("CREATE TABLE t_rpc_bquery (id INT PRIMARY KEY, name VARCHAR, score INT)");
        await client.ExecuteAsync("INSERT INTO t_rpc_bquery VALUES (1, 'Alice', 90), (2, 'Bob', 85), (3, 'Charlie', 92), (4, 'Diana', 88), (5, 'Eve', 95)");

        // 查询全部
        var result = await client.QueryAsync<IDictionary<String, Object>>("SELECT * FROM t_rpc_bquery");
        Assert.NotNull(result);

        // 条件查询
        result = await client.QueryAsync<IDictionary<String, Object>>("SELECT * FROM t_rpc_bquery WHERE score >= 90");
        Assert.NotNull(result);

        // 排序查询
        result = await client.QueryAsync<IDictionary<String, Object>>("SELECT * FROM t_rpc_bquery ORDER BY score DESC");
        Assert.NotNull(result);

        // LIMIT 查询
        result = await client.QueryAsync<IDictionary<String, Object>>("SELECT * FROM t_rpc_bquery ORDER BY score DESC LIMIT 3");
        Assert.NotNull(result);
    }

    [Fact(DisplayName = "集成测试-ADO.NET批量查询")]
    public void TestAdoNetBatchQuery()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();
        using var cmd = conn.CreateCommand();

        cmd.CommandText = "CREATE TABLE t_ado_bquery (id INT PRIMARY KEY, name VARCHAR, score INT)";
        cmd.ExecuteNonQuery();
        cmd.CommandText = "INSERT INTO t_ado_bquery VALUES (1, 'Alice', 90), (2, 'Bob', 85), (3, 'Charlie', 92)";
        cmd.ExecuteNonQuery();

        // 查询全部
        cmd.CommandText = "SELECT * FROM t_ado_bquery ORDER BY id ASC";
        using var reader = cmd.ExecuteReader();
        var rowCount = 0;
        while (reader.Read()) rowCount++;
        Assert.Equal(3, rowCount);
    }

    [Fact(DisplayName = "集成测试-RPC批量修改")]
    public async Task TestRpcBatchUpdate()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        await client.ExecuteAsync("CREATE TABLE t_rpc_bupd (id INT PRIMARY KEY, name VARCHAR, status INT)");
        await client.ExecuteAsync("INSERT INTO t_rpc_bupd VALUES (1, 'Alice', 0), (2, 'Bob', 0), (3, 'Charlie', 0), (4, 'Diana', 1)");

        // 批量更新:将 status=0 的全部改为 1
        var rows = await client.ExecuteAsync("UPDATE t_rpc_bupd SET status = 1 WHERE status = 0");
        Assert.Equal(3, rows);
    }

    [Fact(DisplayName = "集成测试-ADO.NET批量修改")]
    public void TestAdoNetBatchUpdate()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();
        using var cmd = conn.CreateCommand();

        cmd.CommandText = "CREATE TABLE t_ado_bupd (id INT PRIMARY KEY, name VARCHAR, status INT)";
        cmd.ExecuteNonQuery();
        cmd.CommandText = "INSERT INTO t_ado_bupd VALUES (1, 'Alice', 0), (2, 'Bob', 0), (3, 'Charlie', 1)";
        cmd.ExecuteNonQuery();

        cmd.CommandText = "UPDATE t_ado_bupd SET status = 2 WHERE status = 0";
        var rows = cmd.ExecuteNonQuery();
        Assert.Equal(2, rows);
    }

    [Fact(DisplayName = "集成测试-RPC批量删除")]
    public async Task TestRpcBatchDelete()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        await client.ExecuteAsync("CREATE TABLE t_rpc_bdel (id INT PRIMARY KEY, name VARCHAR, age INT)");
        await client.ExecuteAsync("INSERT INTO t_rpc_bdel VALUES (1, 'Alice', 20), (2, 'Bob', 30), (3, 'Charlie', 40), (4, 'Diana', 25)");

        // 批量删除 age < 30 的记录
        var rows = await client.ExecuteAsync("DELETE FROM t_rpc_bdel WHERE age < 30");
        Assert.Equal(2, rows);
    }

    [Fact(DisplayName = "集成测试-ADO.NET批量删除")]
    public void TestAdoNetBatchDelete()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();
        using var cmd = conn.CreateCommand();

        cmd.CommandText = "CREATE TABLE t_ado_bdel (id INT PRIMARY KEY, name VARCHAR, age INT)";
        cmd.ExecuteNonQuery();
        cmd.CommandText = "INSERT INTO t_ado_bdel VALUES (1, 'Alice', 20), (2, 'Bob', 30), (3, 'Charlie', 40)";
        cmd.ExecuteNonQuery();

        cmd.CommandText = "DELETE FROM t_ado_bdel WHERE age >= 30";
        var rows = cmd.ExecuteNonQuery();
        Assert.Equal(2, rows);
    }

    #endregion

    #region 事务测试

    [Fact(DisplayName = "集成测试-RPC事务提交")]
    public async Task TestRpcTransactionCommit()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        // 开始事务
        var txId = await client.BeginTransactionAsync();
        Assert.NotNull(txId);
        Assert.NotEmpty(txId);

        // 提交事务
        var committed = await client.CommitTransactionAsync(txId!);
        Assert.True(committed);
    }

    [Fact(DisplayName = "集成测试-RPC事务回滚")]
    public async Task TestRpcTransactionRollback()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        // 开始事务
        var txId = await client.BeginTransactionAsync();
        Assert.NotNull(txId);

        // 回滚事务
        var rolledBack = await client.RollbackTransactionAsync(txId!);
        Assert.True(rolledBack);
    }

    [Fact(DisplayName = "集成测试-ADO.NET事务提交")]
    public void TestAdoNetTransactionCommit()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();

        // 通过 ADO.NET 开始事务
        using var tx = conn.BeginTransaction();
        Assert.NotNull(tx);
        Assert.IsType<NovaTransaction>(tx);

        var novaDbTx = (NovaTransaction)tx;
        Assert.False(novaDbTx.IsCompleted);
        Assert.NotNull(novaDbTx.TxId);
        Assert.NotEmpty(novaDbTx.TxId);

        // 提交
        tx.Commit();
        Assert.True(novaDbTx.IsCompleted);
    }

    [Fact(DisplayName = "集成测试-ADO.NET事务回滚")]
    public void TestAdoNetTransactionRollback()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();

        using var tx = conn.BeginTransaction();
        var novaDbTx = (NovaTransaction)tx;
        Assert.False(novaDbTx.IsCompleted);

        // 回滚
        tx.Rollback();
        Assert.True(novaDbTx.IsCompleted);
    }

    [Fact(DisplayName = "集成测试-RPC多次事务")]
    public async Task TestRpcMultipleTransactions()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        // 第一个事务:提交
        var txId1 = await client.BeginTransactionAsync();
        Assert.NotNull(txId1);
        var committed = await client.CommitTransactionAsync(txId1!);
        Assert.True(committed);

        // 第二个事务:回滚
        var txId2 = await client.BeginTransactionAsync();
        Assert.NotNull(txId2);
        var rolledBack = await client.RollbackTransactionAsync(txId2!);
        Assert.True(rolledBack);

        // 第三个事务:提交
        var txId3 = await client.BeginTransactionAsync();
        Assert.NotNull(txId3);
        committed = await client.CommitTransactionAsync(txId3!);
        Assert.True(committed);

        // 所有事务 ID 应不同
        Assert.NotEqual(txId1, txId2);
        Assert.NotEqual(txId2, txId3);
    }

    #endregion

    #region 综合端到端测试

    [Fact(DisplayName = "集成测试-完整业务流程")]
    public async Task TestFullBusinessFlow()
    {
        using var client = new NovaClient($"tcp://127.0.0.1:{_port}");
        client.Open();

        // 1. Ping
        var ping = await client.PingAsync();
        Assert.NotNull(ping);

        // 2. 创建表
        await client.ExecuteAsync("CREATE TABLE t_flow (id INT PRIMARY KEY, name VARCHAR NOT NULL, price DOUBLE, quantity INT)");

        // 3. 插入数据
        await client.ExecuteAsync("INSERT INTO t_flow VALUES (1, 'Widget', 9.99, 100)");
        await client.ExecuteAsync("INSERT INTO t_flow VALUES (2, 'Gadget', 19.99, 50)");
        await client.ExecuteAsync("INSERT INTO t_flow VALUES (3, 'Doohickey', 4.99, 200)");

        // 4. 查询所有数据
        var result = await client.QueryAsync<IDictionary<String, Object>>("SELECT * FROM t_flow");
        Assert.NotNull(result);

        // 5. 条件查询
        result = await client.QueryAsync<IDictionary<String, Object>>("SELECT name, price FROM t_flow WHERE price > 5.0");
        Assert.NotNull(result);

        // 6. 更新数据
        var rows = await client.ExecuteAsync("UPDATE t_flow SET quantity = 150 WHERE id = 1");
        Assert.Equal(1, rows);

        // 7. 删除数据
        rows = await client.ExecuteAsync("DELETE FROM t_flow WHERE id = 3");
        Assert.Equal(1, rows);

        // 8. 验证最终状态
        result = await client.QueryAsync<IDictionary<String, Object>>("SELECT COUNT(*) FROM t_flow");
        Assert.NotNull(result);

        // 9. 事务操作
        var txId = await client.BeginTransactionAsync();
        Assert.NotNull(txId);
        var committed = await client.CommitTransactionAsync(txId!);
        Assert.True(committed);

        // 10. DROP 表
        rows = await client.ExecuteAsync("DROP TABLE t_flow");
        Assert.Equal(0, rows);

        // 11. 验证表已删除
        rows = await client.ExecuteAsync("DROP TABLE IF EXISTS t_flow");
        Assert.Equal(0, rows);
    }

    [Fact(DisplayName = "集成测试-ADO.NET完整流程")]
    public void TestAdoNetFullFlow()
    {
        using var conn = new NovaConnection { ConnectionString = $"Server=127.0.0.1;Port={_port}" };
        conn.Open();

        using var cmd = conn.CreateCommand();

        // 1. 创建表
        cmd.CommandText = "CREATE TABLE t_ado_flow (id INT PRIMARY KEY, name VARCHAR NOT NULL, age INT)";
        cmd.ExecuteNonQuery();

        // 2. 插入数据
        cmd.CommandText = "INSERT INTO t_ado_flow VALUES (1, 'Alice', 25), (2, 'Bob', 30), (3, 'Charlie', 35)";
        var rows = cmd.ExecuteNonQuery();
        Assert.Equal(3, rows);

        // 3. 查询数据
        cmd.CommandText = "SELECT * FROM t_ado_flow ORDER BY id ASC";
        using (var reader = cmd.ExecuteReader())
        {
            Assert.True(reader.HasRows);
            Assert.True(reader.Read());
            Assert.True(reader.Read());
            Assert.True(reader.Read());
            Assert.False(reader.Read());
        }

        // 4. 修改数据
        cmd.CommandText = "UPDATE t_ado_flow SET age = 26 WHERE id = 1";
        rows = cmd.ExecuteNonQuery();
        Assert.Equal(1, rows);

        // 5. 删除数据
        cmd.CommandText = "DELETE FROM t_ado_flow WHERE id = 3";
        rows = cmd.ExecuteNonQuery();
        Assert.Equal(1, rows);

        // 6. 事务操作
        using (var tx = conn.BeginTransaction())
        {
            var novaDbTx = (NovaTransaction)tx;
            Assert.False(novaDbTx.IsCompleted);
            tx.Commit();
            Assert.True(novaDbTx.IsCompleted);
        }

        // 7. DROP 表
        cmd.CommandText = "DROP TABLE t_ado_flow";
        cmd.ExecuteNonQuery();
    }

    #endregion
}