feat: 初始化NewLife Studio项目,完成基础框架与数据管理模块
何炳宏 authored at 2026-05-26 12:09:09
5.99 KiB
NewLife.Studio
using NewLife.Studio.AI.Safety;
using Xunit;

namespace NewLife.Studio.AI.Tests;

public class QuerySafetyFilterTests
{
    [Fact]
    public void Validate_SimpleSelect_ReturnsSafe()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("SELECT * FROM users");

        Assert.True(isSafe);
        Assert.Null(reason);
    }

    [Fact]
    public void Validate_SelectWithWhere_ReturnsSafe()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("SELECT id, name FROM users WHERE age > 18");

        Assert.True(isSafe);
        Assert.Null(reason);
    }

    [Fact]
    public void Validate_SelectWithJoin_ReturnsSafe()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate(
            "SELECT u.name, o.total FROM users u INNER JOIN orders o ON u.id = o.user_id");

        Assert.True(isSafe);
        Assert.Null(reason);
    }

    [Fact]
    public void Validate_Explain_ReturnsSafe()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("EXPLAIN SELECT * FROM users");

        Assert.True(isSafe);
        Assert.Null(reason);
    }

    [Fact]
    public void Validate_Pragma_ReturnsSafe()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("PRAGMA table_info(users)");

        Assert.True(isSafe);
        Assert.Null(reason);
    }

    [Fact]
    public void Validate_WithClause_ReturnsSafe()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate(
            "WITH cte AS (SELECT id FROM users) SELECT * FROM cte");

        Assert.True(isSafe);
        Assert.Null(reason);
    }

    [Fact]
    public void Validate_LowerCaseSelect_ReturnsSafe()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("select * from users");

        Assert.True(isSafe);
        Assert.Null(reason);
    }

    [Fact]
    public void Validate_MixedCaseSelect_ReturnsSafe()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("Select * From users");

        Assert.True(isSafe);
        Assert.Null(reason);
    }

    [Fact]
    public void Validate_Insert_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("INSERT INTO users (name) VALUES ('test')");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_Update_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("UPDATE users SET name = 'changed' WHERE id = 1");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_Delete_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("DELETE FROM users WHERE id = 1");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_Drop_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("DROP TABLE users");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_Create_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("CREATE TABLE new_table (id INTEGER PRIMARY KEY)");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_Alter_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("ALTER TABLE users ADD COLUMN email TEXT");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_Truncate_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("TRUNCATE TABLE users");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_MultiStatement_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("SELECT 1; DROP TABLE users");

        Assert.False(isSafe);
        Assert.NotNull(reason);
        Assert.Contains("多语句", reason);
    }

    [Fact]
    public void Validate_MultiStatementWithSemicolonInMiddle_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("SELECT * FROM users;INSERT INTO logs VALUES(1)");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_EmptyString_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("");

        Assert.False(isSafe);
        Assert.NotNull(reason);
        Assert.Contains("只读", reason);
    }

    [Fact]
    public void Validate_WhitespaceString_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("   ");

        Assert.False(isSafe);
        Assert.NotNull(reason);
        Assert.Contains("只读", reason);
    }

    [Fact]
    public void Validate_Grant_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("GRANT SELECT ON users TO someone");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_Merge_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("MERGE INTO users USING source ON users.id = source.id");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_LowerCaseInsert_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("insert into users (name) values ('test')");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }

    [Fact]
    public void Validate_RandomNonSql_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("this is not sql at all");

        Assert.False(isSafe);
        Assert.NotNull(reason);
        Assert.Contains("只读", reason);
    }

    /// <summary>
    /// A SELECT containing a DML keyword in the middle (not via semicolon)
    /// should still be blocked by the keyword-in-text check.
    /// </summary>
    [Fact]
    public void Validate_SelectWithEmbeddedDmlKeyword_ReturnsBlocked()
    {
        var (isSafe, reason) = QuerySafetyFilter.Validate("SELECT * FROM users UPDATE accounts SET balance = 0");

        Assert.False(isSafe);
        Assert.NotNull(reason);
    }
}