在准备环境前提交次全部更改。
This commit is contained in:
139
apps/backend/tests/test_db_viewer_properties.py
Normal file
139
apps/backend/tests/test_db_viewer_properties.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""数据库查看器属性测试(Property-Based Testing)。
|
||||
|
||||
使用 hypothesis 验证数据库查看器的通用正确性属性:
|
||||
- Property 17: SQL 写操作拦截
|
||||
- Property 18: SQL 查询结果行数限制
|
||||
|
||||
测试策略:
|
||||
- Property 17: 生成包含写操作关键词(随机大小写混合)的 SQL 字符串,
|
||||
验证 _WRITE_KEYWORDS 正则表达式能匹配到
|
||||
- Property 18: 生成随机长度的行列表(可能超过 1000 行),
|
||||
验证截取前 _MAX_ROWS 个元素后长度 <= 1000
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key-for-db-viewer-properties")
|
||||
|
||||
from hypothesis import given, settings
|
||||
from hypothesis import strategies as st
|
||||
|
||||
from app.routers.db_viewer import _WRITE_KEYWORDS, _MAX_ROWS
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 通用策略(Strategies)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 写操作关键词列表
|
||||
_WRITE_OPS = ["INSERT", "UPDATE", "DELETE", "DROP", "TRUNCATE"]
|
||||
|
||||
# SQL 前缀/后缀:不含写操作关键词的简单文本
|
||||
_sql_filler_st = st.text(
|
||||
alphabet=st.characters(
|
||||
whitelist_categories=("L", "N", "S"),
|
||||
blacklist_characters="\x00",
|
||||
),
|
||||
min_size=0,
|
||||
max_size=50,
|
||||
)
|
||||
|
||||
# 随机大小写混合的写操作关键词
|
||||
_random_case_keyword_st = st.sampled_from(_WRITE_OPS).flatmap(
|
||||
lambda kw: st.tuples(
|
||||
st.just(kw),
|
||||
st.lists(
|
||||
st.booleans(),
|
||||
min_size=len(kw),
|
||||
max_size=len(kw),
|
||||
),
|
||||
).map(
|
||||
lambda pair: "".join(
|
||||
c.upper() if flag else c.lower()
|
||||
for c, flag in zip(pair[0], pair[1])
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Feature: admin-web-console, Property 17: SQL 写操作拦截
|
||||
# **Validates: Requirements 7.5**
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@settings(max_examples=200)
|
||||
@given(
|
||||
prefix=_sql_filler_st,
|
||||
keyword=_random_case_keyword_st,
|
||||
suffix=_sql_filler_st,
|
||||
)
|
||||
def test_write_keywords_always_detected(prefix, keyword, suffix):
|
||||
"""Property 17: SQL 写操作拦截。
|
||||
|
||||
包含 INSERT、UPDATE、DELETE、DROP、TRUNCATE 关键词(不区分大小写)的
|
||||
SQL 语句,_WRITE_KEYWORDS 正则表达式应能匹配到。
|
||||
|
||||
策略:在随机前缀和后缀之间插入一个随机大小写混合的写操作关键词,
|
||||
用空格分隔以确保 \\b 词边界能匹配。
|
||||
"""
|
||||
# 用空格分隔确保词边界匹配
|
||||
sql = f"{prefix} {keyword} {suffix}"
|
||||
|
||||
match = _WRITE_KEYWORDS.search(sql)
|
||||
assert match is not None, (
|
||||
f"正则表达式未能匹配到写操作关键词:sql={sql!r}, keyword={keyword!r}"
|
||||
)
|
||||
# 匹配到的关键词(转大写后)应在写操作列表中
|
||||
assert match.group(1).upper() in _WRITE_OPS, (
|
||||
f"匹配到的关键词 '{match.group(1)}' 不在写操作列表中"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Feature: admin-web-console, Property 18: SQL 查询结果行数限制
|
||||
# **Validates: Requirements 7.4**
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# 模拟数据库返回的行:每行是一个简单列表
|
||||
_row_st = st.lists(
|
||||
st.one_of(st.integers(), st.text(max_size=20), st.none()),
|
||||
min_size=1,
|
||||
max_size=5,
|
||||
)
|
||||
|
||||
# 行列表策略:0 到 3000 行(覆盖超过 _MAX_ROWS 的情况)
|
||||
_rows_st = st.lists(_row_st, min_size=0, max_size=3000)
|
||||
|
||||
|
||||
@settings(max_examples=200)
|
||||
@given(rows=_rows_st)
|
||||
def test_row_count_never_exceeds_max(rows):
|
||||
"""Property 18: SQL 查询结果行数限制。
|
||||
|
||||
对任意长度的行列表,取前 _MAX_ROWS 个元素后,
|
||||
结果长度应 <= 1000。
|
||||
|
||||
这等价于 cur.fetchmany(_MAX_ROWS) 的行为:
|
||||
数据库游标最多返回 _MAX_ROWS 行。
|
||||
"""
|
||||
# 模拟 fetchmany(_MAX_ROWS) 的行为
|
||||
truncated = rows[:_MAX_ROWS]
|
||||
|
||||
assert len(truncated) <= _MAX_ROWS, (
|
||||
f"截取后行数 {len(truncated)} 超过上限 {_MAX_ROWS}"
|
||||
)
|
||||
|
||||
# 额外验证:如果原始行数 <= _MAX_ROWS,截取后应保留全部
|
||||
if len(rows) <= _MAX_ROWS:
|
||||
assert len(truncated) == len(rows), (
|
||||
f"原始行数 {len(rows)} <= {_MAX_ROWS},截取后应保留全部,"
|
||||
f"实际 {len(truncated)}"
|
||||
)
|
||||
|
||||
# 额外验证:如果原始行数 > _MAX_ROWS,截取后应恰好为 _MAX_ROWS
|
||||
if len(rows) > _MAX_ROWS:
|
||||
assert len(truncated) == _MAX_ROWS, (
|
||||
f"原始行数 {len(rows)} > {_MAX_ROWS},截取后应恰好为 {_MAX_ROWS},"
|
||||
f"实际 {len(truncated)}"
|
||||
)
|
||||
Reference in New Issue
Block a user