""" 集成测试: 审核策略是否生效 覆盖: DDL/DML/安全/性能规则 → Redis/MongoDB规则 → 开关控制 → 评分系统 """ import pytest from httpx import AsyncClient def _auth(token: str) -> dict: return {"Authorization": f"Bearer {token}"} async def _setup(admin_token, client): """创建测试实例""" inst = await client.post("/api/v1/instances", json={ "name": "审核测试MySQL", "db_type": "mysql", "host": "127.0.0.1", "port": 3306, "username": "root", "password": "test", "database": "test_db", "env": "production", }, headers=_auth(admin_token)) assert inst.status_code == 201 mysql_id = inst.json()["id"] redis = await client.post("/api/v1/instances", json={ "name": "审核测试Redis", "db_type": "redis", "host": "127.0.0.1", "port": 6379, "username": "", "password": "test", "database": "0", "env": "production", }, headers=_auth(admin_token)) assert redis.status_code == 201 redis_id = redis.json()["id"] return mysql_id, redis_id async def _audit(client, token, sql, instance_id, database="test_db"): return await client.post("/api/v1/tickets/audit", json={ "sql_content": sql, "instance_id": instance_id, "database": database, }, headers=_auth(token)) # ══════════════════════════════════════════════════════════════ # 1. DDL 规则生效验证 # ══════════════════════════════════════════════════════════════ class TestDDLRulesEnforcement: async def test_create_table_no_pk(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "CREATE TABLE t1 (name VARCHAR(50))", mysql_id) assert resp.status_code == 200 data = resp.json() assert data["score"] < 100 # 无主键应该扣分 async def test_drop_table_production(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "DROP TABLE users", mysql_id) assert resp.status_code == 200 data = resp.json() assert data["score"] < 80 # DROP TABLE 应该低分 async def test_truncate_warning(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "TRUNCATE TABLE users", mysql_id) assert resp.status_code == 200 # TRUNCATE 应该触发警告或错误 data = resp.json() assert data["score"] < 100 async def test_float_type_warning(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "CREATE TABLE t (id INT PRIMARY KEY, price FLOAT)", mysql_id) assert resp.status_code == 200 data = resp.json() assert data["score"] < 100 # FLOAT 类型应扣分 # ══════════════════════════════════════════════════════════════ # 2. DML 规则生效验证 # ══════════════════════════════════════════════════════════════ class TestDMLRulesEnforcement: async def test_update_no_where(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "UPDATE users SET name='test'", mysql_id) assert resp.status_code == 200 data = resp.json() assert data["score"] < 60 # 无WHERE的UPDATE应严重扣分 async def test_delete_no_where(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "DELETE FROM users", mysql_id) assert resp.status_code == 200 data = resp.json() assert data["score"] < 60 async def test_insert_with_where_ok(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "UPDATE users SET name='test' WHERE id=1", mysql_id) assert resp.status_code == 200 # 有WHERE的UPDATE应该高分 assert resp.json()["score"] >= 70 # ══════════════════════════════════════════════════════════════ # 3. 性能规则 # ══════════════════════════════════════════════════════════════ class TestPerformanceRules: async def test_select_star(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "SELECT * FROM users WHERE id=1", mysql_id) assert resp.status_code == 200 # SELECT * 应该扣分 assert resp.json()["score"] < 100 async def test_clean_select(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "SELECT id, name FROM users WHERE id=1", mysql_id) assert resp.status_code == 200 assert resp.json()["score"] >= 80 async def test_join_no_on(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "SELECT * FROM users JOIN orders", mysql_id) assert resp.status_code == 200 assert resp.json()["score"] < 100 async def test_large_in_list(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) ids = ",".join(str(i) for i in range(200)) resp = await _audit(client, admin_token, f"SELECT * FROM users WHERE id IN ({ids})", mysql_id) assert resp.status_code == 200 assert resp.json()["score"] < 100 # ══════════════════════════════════════════════════════════════ # 4. Redis 审核规则 # ══════════════════════════════════════════════════════════════ class TestRedisAuditRules: async def test_flushall_critical(self, client, admin_token): _, redis_id = await _setup(admin_token, client) resp = await _audit(client, admin_token, "FLUSHALL", redis_id, "db0") assert resp.status_code == 200 assert resp.json()["risk_level"] in ("high", "critical") async def test_flushdb_critical(self, client, admin_token): _, redis_id = await _setup(admin_token, client) resp = await _audit(client, admin_token, "FLUSHDB", redis_id, "db0") assert resp.status_code == 200 assert resp.json()["risk_level"] in ("high", "critical") async def test_keys_pattern_high(self, client, admin_token): _, redis_id = await _setup(admin_token, client) resp = await _audit(client, admin_token, "KEYS *", redis_id, "db0") assert resp.status_code == 200 assert resp.json()["risk_level"] in ("medium", "high", "critical") async def test_get_low_risk(self, client, admin_token): _, redis_id = await _setup(admin_token, client) resp = await _audit(client, admin_token, "GET mykey", redis_id, "db0") assert resp.status_code == 200 assert resp.json()["risk_level"] in ("low", "medium") async def test_config_set_warning(self, client, admin_token): _, redis_id = await _setup(admin_token, client) resp = await _audit(client, admin_token, "CONFIG SET maxmemory 100mb", redis_id, "db0") assert resp.status_code == 200 # ══════════════════════════════════════════════════════════════ # 5. 审核评分系统 # ══════════════════════════════════════════════════════════════ class TestAuditScoring: async def test_clean_sql_high_score(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "SELECT id, name FROM users WHERE id=1 LIMIT 10", mysql_id) assert resp.status_code == 200 assert resp.json()["score"] >= 80 async def test_dangerous_sql_low_score(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) resp = await _audit(client, admin_token, "DELETE FROM users", mysql_id) assert resp.status_code == 200 assert resp.json()["score"] < 60 async def test_score_between_0_100(self, client, admin_token): mysql_id, _ = await _setup(admin_token, client) for sql in ["SELECT 1", "DROP TABLE t", "UPDATE t SET a=1", "SELECT * FROM t WHERE id=1"]: resp = await _audit(client, admin_token, sql, mysql_id) assert resp.status_code == 200 score = resp.json()["score"] assert 0 <= score <= 100, f"Score {score} out of range for: {sql}" # ══════════════════════════════════════════════════════════════ # 6. 审核规则 CRUD + 开关 # ══════════════════════════════════════════════════════════════ class TestAuditRuleManagement: async def test_list_rules(self, client, admin_token): resp = await client.get("/api/v1/audit-rules", headers=_auth(admin_token)) assert resp.status_code == 200 rules = resp.json() assert isinstance(rules, list) assert len(rules) > 0 # 应有种子数据 async def test_create_and_toggle_rule(self, client, admin_token): # 创建自定义规则 resp = await client.post("/api/v1/audit-rules", json={ "rule_id": "custom_test_001", "name": "自定义测试规则", "type": "DML", "severity": "warning", "enabled": True, "description": "集成测试用规则", }, headers=_auth(admin_token)) assert resp.status_code in (200, 201) rule_id = resp.json().get("id") if rule_id: # 禁用规则 resp2 = await client.put(f"/api/v1/audit-rules/{rule_id}", json={ "enabled": False, }, headers=_auth(admin_token)) assert resp2.status_code == 200 # 重新启用 resp3 = await client.put(f"/api/v1/audit-rules/{rule_id}", json={ "enabled": True, }, headers=_auth(admin_token)) assert resp3.status_code == 200 async def test_rule_type_filter(self, client, admin_token): for rule_type in ["DDL", "DML", "security", "performance"]: resp = await client.get(f"/api/v1/audit-rules?type={rule_type}", headers=_auth(admin_token)) assert resp.status_code == 200 async def test_rules_stats(self, client, admin_token): resp = await client.get("/api/v1/audit-rules/stats", headers=_auth(admin_token)) # stats 可能存在也可能不存在 assert resp.status_code in (200, 404)