"""风险等级评估 + 维护窗口 + 通知脱敏 + SQL items 测试。""" import json import pytest from unittest.mock import patch, MagicMock # ===== 风险等级评估测试 ===== class TestRiskEvaluator: """风险等级自动计算测试。""" def test_select_is_low(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("SELECT * FROM users WHERE id = 1") == "low" def test_show_is_low(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("SHOW TABLES") == "low" def test_explain_is_low(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("EXPLAIN SELECT * FROM users") == "low" def test_create_table_is_medium(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("CREATE TABLE t1 (id INT)") == "medium" def test_alter_table_is_medium(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("ALTER TABLE users ADD COLUMN age INT") == "medium" def test_insert_with_where_is_medium(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level( "INSERT INTO users (name) VALUES ('test')", sql_type="DML", ) == "medium" def test_update_without_where_is_high(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("UPDATE users SET status = 1") == "high" def test_delete_without_where_is_high(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("DELETE FROM users") == "high" def test_alter_drop_column_is_high(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("ALTER TABLE users DROP COLUMN age") == "high" def test_alter_modify_column_is_high(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("ALTER TABLE users MODIFY COLUMN age BIGINT") == "high" def test_drop_table_is_critical(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("DROP TABLE users") == "critical" def test_truncate_is_critical(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("TRUNCATE TABLE users") == "critical" def test_drop_database_is_critical(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("DROP DATABASE mydb") == "critical" def test_many_statements_is_high(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level( "SELECT 1", statement_count=10 ) == "high" def test_multiple_audit_errors_is_high(self): from app.services.risk_evaluator import evaluate_risk_level findings = [ {"level": "error", "rule_id": "R1"}, {"level": "error", "rule_id": "R2"}, ] assert evaluate_risk_level( "UPDATE users SET x=1 WHERE id=1", sql_type="DML", audit_findings=findings, ) == "high" def test_delete_with_where_no_limit_is_medium(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level( "DELETE FROM users WHERE id > 100" ) == "medium" def test_delete_with_where_and_limit_is_low(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level( "DELETE FROM users WHERE id > 100 LIMIT 10" ) == "low" # Redis 风险 def test_redis_flushall_is_critical(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("FLUSHALL", db_type="redis") == "critical" def test_redis_flushdb_is_critical(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("FLUSHDB", db_type="redis") == "critical" def test_redis_set_is_medium(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("SET key value", db_type="redis") == "medium" def test_redis_get_is_low(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("GET key", db_type="redis") == "low" # MongoDB 风险 def test_mongo_drop_database_is_critical(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("db.dropDatabase()", db_type="mongodb") == "critical" def test_mongo_drop_database_upper_is_critical(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("DROP DATABASE mydb", db_type="mongodb") == "critical" def test_mongo_insert_is_medium(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("db.users.insertOne({name: 'test'})", db_type="mongodb") == "medium" def test_mongo_find_is_low(self): from app.services.risk_evaluator import evaluate_risk_level assert evaluate_risk_level("db.users.find({})", db_type="mongodb") == "low" # ===== 风险等级配置测试 ===== class TestRiskLevelConfig: """风险等级配置验证。""" def test_all_levels_have_config(self): from app.services.risk_evaluator import RISK_LEVEL_CONFIG for level in ["low", "medium", "high", "critical"]: assert level in RISK_LEVEL_CONFIG cfg = RISK_LEVEL_CONFIG[level] assert "label" in cfg assert "color" in cfg assert "min_approvers" in cfg def test_high_and_critical_require_2_approvers(self): from app.services.risk_evaluator import RISK_LEVEL_CONFIG assert RISK_LEVEL_CONFIG["high"]["min_approvers"] >= 2 assert RISK_LEVEL_CONFIG["critical"]["min_approvers"] >= 2 # ===== 通知脱敏测试 ===== class TestNotificationMasking: """通知内容脱敏测试。""" def test_mask_password_in_sql_preview(self): from app.services.notification_sender import NotificationSender data = {"sql_preview": "SET password='secret123' WHERE id=1"} masked = NotificationSender._mask_sensitive_data(data) assert "secret123" not in masked["sql_preview"] assert "***" in masked["sql_preview"] def test_mask_drop_table_in_sql_preview(self): from app.services.notification_sender import NotificationSender data = {"sql_preview": "DROP TABLE users"} masked = NotificationSender._mask_sensitive_data(data) assert "***" in masked["sql_preview"] def test_mask_error_field(self): from app.services.notification_sender import NotificationSender data = {"error": "Connection failed: password='mysecret'"} masked = NotificationSender._mask_sensitive_data(data) assert "mysecret" not in masked["error"] def test_truncate_long_sql_preview(self): from app.services.notification_sender import NotificationSender long_sql = "SELECT * FROM users WHERE " + "x=1 AND " * 50 data = {"sql_preview": long_sql} masked = NotificationSender._mask_sensitive_data(data) assert len(masked["sql_preview"]) <= 110 # 100 + "..." def test_non_sensitive_data_unchanged(self): from app.services.notification_sender import NotificationSender data = {"title": "测试工单", "applicant": "张三"} masked = NotificationSender._mask_sensitive_data(data) assert masked["title"] == "测试工单" assert masked["applicant"] == "张三" # ===== 维护窗口测试 ===== class TestMaintenanceWindow: """维护窗口逻辑测试。""" def test_no_config_allows_execution(self): """无配置时允许执行。""" from app.api.v1.endpoints.ticket import _check_maintenance_window from unittest.mock import AsyncMock import asyncio mock_db = AsyncMock() mock_result = MagicMock() mock_result.scalar_one_or_none.return_value = None mock_db.execute.return_value = mock_result async def run(): result = await _check_maintenance_window(mock_db) assert result is None asyncio.get_event_loop().run_until_complete(run()) def test_disabled_window_allows_execution(self): """disabled 的窗口允许执行。""" from app.api.v1.endpoints.ticket import _check_maintenance_window from app.models.system_setting import SystemSetting from unittest.mock import AsyncMock import asyncio setting = SystemSetting(key="maintenance_window", value='{"enabled": false}') mock_db = AsyncMock() mock_result = MagicMock() mock_result.scalar_one_or_none.return_value = setting mock_db.execute.return_value = mock_result async def run(): result = await _check_maintenance_window(mock_db) assert result is None asyncio.get_event_loop().run_until_complete(run()) # ===== SQL 类型分类测试 ===== class TestClassifySqlType: """SQL 类型分类辅助函数测试。""" def test_select_is_dql(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("SELECT * FROM t") == "DQL" def test_insert_is_dml(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("INSERT INTO t VALUES (1)") == "DML" def test_update_is_dml(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("UPDATE t SET x=1") == "DML" def test_delete_is_dml(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("DELETE FROM t") == "DML" def test_create_is_ddl(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("CREATE TABLE t (id INT)") == "DDL" def test_alter_is_ddl(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("ALTER TABLE t ADD COLUMN x INT") == "DDL" def test_drop_is_ddl(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("DROP TABLE t") == "DDL" def test_grant_is_dcl(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("GRANT SELECT ON t TO user") == "DCL" def test_redis_set(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("SET key value") == "SET" def test_redis_get(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("GET key") == "SET" def test_unknown(self): from app.api.v1.endpoints.ticket import _classify_sql_type assert _classify_sql_type("UNKNOWN COMMAND") == "OTHER"