在准备环境前提交次全部更改。

This commit is contained in:
Neo
2026-02-19 08:35:13 +08:00
parent ded6dfb9d8
commit 4eac07da47
1387 changed files with 6107191 additions and 33002 deletions

View 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)}"
)