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);
}
}
|