Files
opsxsql/tests/test_approval_flow_comprehensive.py
T
logan 88e5f4e7ee feat: Go后端LDAP真实对接 — AES-256-GCM密码加解密 + 登录LDAP fallback
- 新增 crypto.go: AES-256-GCM 加解密,与 Python backend 兼容
- auth.go: Login 添加 LDAP fallback(本地优先→LDAP绑定→自动创建本地用户)
- ldap.go: 实现真实 LDAP 连接/用户搜索/密码验证/OU列表/用户列表
- 所有 LDAP 操作解密 admin_password 后使用
- 新增/更新密码存储时加密
- router.go: 补充 delete-user 路由
- 添加 go-ldap/v3 依赖
2026-05-25 23:32:15 +08:00

340 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""审批流全面场景测试 — MySQL + Redis。
覆盖:
- MySQL: 基础查询/写入、高危DDL、性能规则、安全规则
- Redis: 基础读写、高危命令、特殊场景
- 审核引擎规则命中 + 风险等级自动计算 + 审批流匹配
"""
import pytest
import asyncio
from unittest.mock import AsyncMock, MagicMock
def _audit_sql(sql, db_type="mysql"):
"""辅助:直接调审核引擎,返回 findings。"""
from app.services.audit_engine_v2 import SQLAuditEngineV2
async def _run():
engine = SQLAuditEngineV2(db_session=None, env="development")
report = await engine.audit(sql, db_type=db_type)
return report
return asyncio.get_event_loop().run_until_complete(_run())
def _eval_risk(sql, db_type="mysql", sql_type="", table_sizes=None):
"""辅助:直接调风险评估。"""
from app.services.risk_evaluator import evaluate_risk_level
return evaluate_risk_level(sql, db_type=db_type, sql_type=sql_type, table_sizes=table_sizes)
# ══════════════════════════════════════════════
# 一、MySQL 基础功能测试
# ══════════════════════════════════════════════
class TestMySQLBasic:
"""MySQL 基础功能 — 查询/写入/批量/无WHERE。"""
def test_01_simple_select_low_risk(self):
"""#1 简单查询 — risk=low。"""
risk = _eval_risk("SELECT * FROM users LIMIT 10;", db_type="mysql", sql_type="DQL")
assert risk == "low"
def test_02_simple_insert_medium(self):
"""#2 简单 INSERT — risk=medium。"""
risk = _eval_risk("INSERT INTO users(name) VALUES('test');", db_type="mysql", sql_type="DML")
assert risk == "medium"
def test_03_batch_insert_high(self):
"""#3 批量 INSERT(1000条) — risk=high(statement_count>5)。"""
sql = "INSERT INTO users(name) VALUES('test');\n" * 1000
from app.services.risk_evaluator import evaluate_risk_level
import sqlparse
stmt_list = [s.strip() for s in sqlparse.split(sql) if s.strip()]
risk = evaluate_risk_level(sql, db_type="mysql", sql_type="DML", statement_count=len(stmt_list))
assert risk in ("high", "medium")
def test_04_update_no_where_high_risk(self):
"""#4 UPDATE 无 WHERE — risk=high。"""
risk = _eval_risk("UPDATE users SET status=1;", db_type="mysql")
assert risk == "high", f"预期 high,实际 {risk}"
def test_05_delete_no_where_high_risk(self):
"""#5 DELETE 无 WHERE — risk=high。"""
risk = _eval_risk("DELETE FROM users;", db_type="mysql")
assert risk == "high", f"预期 high,实际 {risk}"
# ══════════════════════════════════════════════
# 二、MySQL 高危 DDL 操作
# ══════════════════════════════════════════════
class TestMySQLDDL:
"""MySQL 高危 DDL — DROP/TRUNCATE/ALTER。"""
def test_06_drop_table_critical(self):
"""#6 DROP TABLE — risk=critical。"""
risk = _eval_risk("DROP TABLE users;", db_type="mysql")
assert risk == "critical"
def test_06_drop_table_audit_blocked(self):
"""#6 DROP TABLE — 审核引擎拦截(DDL-004 / MYSQL-DDL-016)。"""
report = _audit_sql("DROP TABLE users;", db_type="mysql")
findings = report.findings if hasattr(report, 'findings') else []
codes = [f.rule_code for f in findings]
# DDL-004(禁止DROP TABLE) 或 MYSQL-DDL-016 都算拦截
assert any(c in codes for c in ("DDL-004", "MYSQL-DDL-016")), f"DROP TABLE 应被拦截,实际命中: {codes}"
def test_07_truncate_critical(self):
"""#7 TRUNCATE — risk=critical。"""
risk = _eval_risk("TRUNCATE TABLE orders;", db_type="mysql")
assert risk == "critical"
def test_08_alter_large_table_risk_upgrade(self):
"""#8 ALTER 大表(>10万行) — risk 升级为 high。"""
risk = _eval_risk(
"ALTER TABLE orders ADD COLUMN new_col varchar(255);",
db_type="mysql", sql_type="DDL",
table_sizes={"orders": 1_000_000}
)
assert risk == "high"
def test_08_alter_small_table_medium(self):
"""#8 ALTER 小表 — risk=medium。"""
risk = _eval_risk(
"ALTER TABLE users ADD COLUMN age INT;",
db_type="mysql", sql_type="DDL",
table_sizes={"users": 50}
)
assert risk == "medium"
def test_09_modify_column_warning(self):
"""#9 MODIFY COLUMN — 审核引擎应有警告。"""
report = _audit_sql("ALTER TABLE users MODIFY id bigint;", db_type="mysql")
findings = report.findings if hasattr(report, 'findings') else []
assert len(findings) > 0, "MODIFY COLUMN 应触发审核规则"
def test_10_create_index_medium(self):
"""#10 CREATE INDEX — risk=medium。"""
risk = _eval_risk(
"CREATE INDEX idx_name ON users(name);",
db_type="mysql", sql_type="DDL"
)
assert risk == "medium"
# ══════════════════════════════════════════════
# 三、MySQL 性能与安全规则
# ══════════════════════════════════════════════
class TestMySQLPerfSecurity:
"""MySQL 性能与安全规则检测。"""
def test_select_star_production_warning(self):
"""SELECT * 应触发审核警告。"""
report = _audit_sql("SELECT * FROM users;", db_type="mysql")
findings = report.findings if hasattr(report, 'findings') else []
codes = [f.rule_code for f in findings]
# DQL-001 或类似规则
assert len(findings) > 0, f"SELECT * 应触发审核规则,实际命中: {codes}"
def test_multi_join_warning(self):
"""多表 JOIN(>3) 应触发警告。"""
sql = """
SELECT * FROM t1
JOIN t2 ON t1.id = t2.t1_id
JOIN t3 ON t2.id = t3.t2_id
JOIN t4 ON t3.id = t4.t3_id
JOIN t5 ON t4.id = t5.t4_id
"""
report = _audit_sql(sql, db_type="mysql")
findings = report.findings if hasattr(report, 'findings') else []
# 可能触发 JOIN 数量限制规则
assert len(findings) > 0, "5表 JOIN 应触发审核规则"
def test_sensitive_column_warning(self):
"""敏感字段操作(password)应触发警告。"""
sql = "SELECT password, id_card FROM users;"
report = _audit_sql(sql, db_type="mysql")
findings = report.findings if hasattr(report, 'findings') else []
# 安全规则应检测敏感字段
assert len(findings) > 0, "敏感字段应触发安全规则"
# ══════════════════════════════════════════════
# 四、Redis 基础命令测试
# ══════════════════════════════════════════════
class TestRedisBasic:
"""Redis 基础命令 — 读/写/批量/KEYS/FLUSH。"""
def test_01_redis_get_low(self):
"""Redis GET — risk=low。"""
risk = _eval_risk("GET user:1001", db_type="redis")
assert risk == "low"
def test_02_redis_set_medium(self):
"""Redis SET — risk=medium。"""
risk = _eval_risk("SET user:999 test EX 3600", db_type="redis")
assert risk == "medium"
def test_04_redis_keys_high(self):
"""Redis KEYS * — 生产环境应拦截。"""
# KEYS 不是写命令但极其危险
# 当前 risk_evaluator 只按命令前缀判断,KEYS 不在写命令列表
# 但审核引擎应该有规则拦截
report = _audit_sql("KEYS user:*", db_type="redis")
findings = report.findings if hasattr(report, 'findings') else []
# 至少应该有警告(如果审核引擎有 Redis 规则的话)
# 这里主要验证不 crash
assert report is not None
def test_05_redis_flushall_critical(self):
"""Redis FLUSHALL — risk=critical。"""
risk = _eval_risk("FLUSHALL", db_type="redis")
assert risk == "critical"
def test_05_redis_flushdb_critical(self):
"""Redis FLUSHDB — risk=critical。"""
risk = _eval_risk("FLUSHDB", db_type="redis")
assert risk == "critical"
def test_05_redis_flushdb_audit_blocked(self):
"""Redis FLUSHDB — 审核引擎拦截。"""
report = _audit_sql("FLUSHDB", db_type="redis")
findings = report.findings if hasattr(report, 'findings') else []
codes = [f.rule_code for f in findings]
# Redis 规则应拦截 FLUSHDB
assert len(findings) > 0, f"FLUSHDB 应被拦截,实际: {codes}"
# ══════════════════════════════════════════════
# 五、Redis 高危操作测试
# ══════════════════════════════════════════════
class TestRedisDangerous:
"""Redis 高危命令 — DEL/CONFIG/EVAL/BGSAVE。"""
def test_06_redis_del_high(self):
"""Redis DEL 大量Key — risk=high。"""
risk = _eval_risk("DEL key1 key2 key3 key4 key5", db_type="redis")
assert risk == "high"
def test_07_redis_rename_high(self):
"""Redis RENAME — risk=high。"""
risk = _eval_risk("RENAME oldkey newkey", db_type="redis")
assert risk == "high"
def test_08_redis_config_set_warning(self):
"""Redis CONFIG SET — 审核引擎应有规则。"""
report = _audit_sql("CONFIG SET maxmemory 10gb", db_type="redis")
findings = report.findings if hasattr(report, 'findings') else []
# 验证不 crash
assert report is not None
def test_09_redis_eval_script_warning(self):
"""Redis EVAL Lua 脚本 — 应检测危险模式。"""
report = _audit_sql('EVAL "redis.call(\'FLUSHALL\')" 0', db_type="redis")
findings = report.findings if hasattr(report, 'findings') else []
# FLUSHALL 在 EVAL 中应被检测
assert len(findings) > 0, "EVAL 中包含 FLUSHALL 应被拦截"
def test_10_redis_bgsave_low(self):
"""Redis BGSAVE — risk=low(只读管理命令)。"""
risk = _eval_risk("BGSAVE", db_type="redis")
assert risk == "low"
# ══════════════════════════════════════════════
# 六、风险等级边界测试
# ══════════════════════════════════════════════
class TestRiskLevelBoundary:
"""风险等级边界值测试。"""
def test_critical_drop_database(self):
risk = _eval_risk("DROP DATABASE test_e2e;", db_type="mysql")
assert risk == "critical"
def test_critical_drop_schema(self):
risk = _eval_risk("DROP SCHEMA public;", db_type="mysql")
assert risk == "critical"
def test_high_alter_drop_column(self):
risk = _eval_risk("ALTER TABLE users DROP COLUMN age;", db_type="mysql")
assert risk == "high"
def test_high_alter_drop_primary_key(self):
risk = _eval_risk("ALTER TABLE users DROP PRIMARY KEY;", db_type="mysql")
assert risk == "high"
def test_high_delete_with_where_medium(self):
"""DELETE 有 WHERE 但无 LIMIT — medium。"""
risk = _eval_risk("DELETE FROM users WHERE id = 1;", db_type="mysql")
assert risk == "medium"
def test_low_select_with_where(self):
"""SELECT 查询 — low。"""
risk = _eval_risk("SELECT id, name FROM users WHERE id = 1;", db_type="mysql")
assert risk == "low"
def test_medium_insert(self):
"""INSERT — medium。"""
risk = _eval_risk("INSERT INTO users(name) VALUES('test');", db_type="mysql", sql_type="DML")
assert risk == "medium"
def test_medium_grant(self):
"""GRANT — medium。"""
risk = _eval_risk("GRANT SELECT ON test.* TO 'reader'@'%';", db_type="mysql")
assert risk == "medium"
def test_medium_create_table(self):
"""CREATE TABLE — medium。"""
risk = _eval_risk("CREATE TABLE test_table (id INT PRIMARY KEY);", db_type="mysql")
assert risk == "medium"
def test_table_sizes_exact_threshold(self):
"""大表阈值:99999行不升级,100001行升级。"""
risk_below = _eval_risk(
"ALTER TABLE t ADD COLUMN x INT;",
db_type="mysql", sql_type="DDL",
table_sizes={"t": 99_999}
)
assert risk_below == "medium"
risk_above = _eval_risk(
"ALTER TABLE t ADD COLUMN x INT;",
db_type="mysql", sql_type="DDL",
table_sizes={"t": 100_001}
)
assert risk_above == "high"
# ══════════════════════════════════════════════
# 七、MongoDB 风险评估
# ══════════════════════════════════════════════
class TestMongoDBRisk:
"""MongoDB 操作风险评估。"""
def test_mongo_drop_database_critical(self):
risk = _eval_risk("db.dropDatabase()", db_type="mongodb")
assert risk == "critical"
def test_mongo_drop_collection_high(self):
risk = _eval_risk("db.users.drop()", db_type="mongodb")
assert risk == "high"
def test_mongo_insert_medium(self):
risk = _eval_risk("db.users.insertOne({name: 'test'})", db_type="mongodb")
assert risk == "medium"
def test_mongo_find_low(self):
risk = _eval_risk("db.users.find({})", db_type="mongodb")
assert risk == "low"
def test_mongo_delete_many_high(self):
risk = _eval_risk("db.users.deleteMany({})", db_type="mongodb")
assert risk == "high"