首次提交
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
测试配置读取功能。
|
||||
使用 mock 配置文件,不依赖真实 config.json。
|
||||
"""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from paste.core import config
|
||||
|
||||
|
||||
class TestConfiguration:
|
||||
"""配置管理测试"""
|
||||
|
||||
def test_load_config_with_mock_file(self, temp_config_file):
|
||||
"""使用临时配置文件测试加载"""
|
||||
with patch.object(config, 'load_config', wraps=config.load_config):
|
||||
# 模拟配置文件路径
|
||||
with patch('paste.core.config.os.path.abspath') as mock_abspath:
|
||||
mock_abspath.return_value = str(temp_config_file)
|
||||
cfg = config.load_config()
|
||||
assert isinstance(cfg, dict)
|
||||
assert 'db_engine' in cfg
|
||||
|
||||
def test_get_config_by_path_existing(self, mock_config_dict):
|
||||
"""测试读取存在的配置项"""
|
||||
with patch('paste.core.config.GLOBAL_CONFIG', mock_config_dict):
|
||||
result = config.get_config_by_path('db_engine.engine')
|
||||
assert result == "sqlite+pysqlite:///:memory:"
|
||||
|
||||
def test_get_config_by_path_nested(self, mock_config_dict):
|
||||
"""测试读取深层嵌套配置"""
|
||||
with patch('paste.core.config.GLOBAL_CONFIG', mock_config_dict):
|
||||
result = config.get_config_by_path('redis.connection.url')
|
||||
assert result == "redis://localhost:6379/15"
|
||||
|
||||
def test_get_config_by_path_with_default(self, mock_config_dict):
|
||||
"""测试带默认值的配置读取"""
|
||||
with patch('paste.core.config.GLOBAL_CONFIG', mock_config_dict):
|
||||
result = config.get_config_by_path('nonexistent.key', default='fallback')
|
||||
assert result == 'fallback'
|
||||
|
||||
def test_get_config_by_path_missing_without_default(self, mock_config_dict):
|
||||
"""测试缺失配置项且无默认值时抛出异常"""
|
||||
with patch('paste.core.config.GLOBAL_CONFIG', mock_config_dict):
|
||||
with pytest.raises(AssertionError):
|
||||
config.get_config_by_path('nonexistent.key')
|
||||
|
||||
def test_get_config_shortcut(self, mock_config_dict):
|
||||
"""测试 get_config 快捷方法"""
|
||||
with patch('paste.core.config.GLOBAL_CONFIG', mock_config_dict):
|
||||
result = config.get_config('tornado.demo.port')
|
||||
assert result == 9000
|
||||
|
||||
def test_get_config_default_none(self, mock_config_dict):
|
||||
"""测试 get_config 默认值 None 的情况"""
|
||||
with patch('paste.core.config.GLOBAL_CONFIG', mock_config_dict):
|
||||
with pytest.raises(AssertionError):
|
||||
config.get_config('completely.nonexistent')
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
测试 JWT 令牌编解码功能。
|
||||
使用 mock 配置,不依赖真实密钥文件。
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from paste.security.token import encode_token, decode_token
|
||||
|
||||
|
||||
class TestJwtToken:
|
||||
"""JWT 令牌测试"""
|
||||
|
||||
def test_encode_decode_basic(self):
|
||||
"""基础编解码测试"""
|
||||
payload = {
|
||||
'user_id': 123,
|
||||
'username': 'test_user',
|
||||
'role': 'admin',
|
||||
}
|
||||
token = encode_token(**payload)
|
||||
assert token is not None
|
||||
assert isinstance(token, str)
|
||||
assert len(token) > 0
|
||||
|
||||
decoded = decode_token(token)
|
||||
assert decoded is not None
|
||||
assert decoded.get('params', {}).get('user_id') == 123
|
||||
|
||||
def test_token_contains_expected_fields(self):
|
||||
"""验证 token 包含必要字段"""
|
||||
payload = {'user_id': 456, 'username': 'demo'}
|
||||
token = encode_token(**payload)
|
||||
decoded = decode_token(token)
|
||||
|
||||
# 标准 JWT 字段
|
||||
assert 'iss' in decoded, "Token should have issuer"
|
||||
assert 'iat' in decoded, "Token should have issued-at time"
|
||||
assert 'exp' in decoded, "Token should have expiration time"
|
||||
|
||||
# 自定义字段
|
||||
params = decoded.get('params', {})
|
||||
assert params.get('user_id') == 456
|
||||
assert params.get('username') == 'demo'
|
||||
|
||||
def test_token_expiration(self):
|
||||
"""验证 token 过期机制"""
|
||||
payload = {
|
||||
'user_id': 789,
|
||||
'username': 'expired_user',
|
||||
'exp': int(time.time()) - 3600, # 1小时前过期
|
||||
}
|
||||
token = encode_token(**payload)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
decode_token(token)
|
||||
|
||||
def test_token_tampering(self):
|
||||
"""验证 token 防篡改"""
|
||||
payload = {'user_id': 999, 'username': 'hacker'}
|
||||
token = encode_token(**payload)
|
||||
|
||||
# 篡改 token
|
||||
tampered_token = token[:-5] + 'XXXXX'
|
||||
|
||||
with pytest.raises(Exception):
|
||||
decode_token(tampered_token)
|
||||
|
||||
def test_empty_payload(self):
|
||||
"""空 payload 处理"""
|
||||
token = encode_token()
|
||||
decoded = decode_token(token)
|
||||
assert decoded is not None
|
||||
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
测试分页逻辑。
|
||||
无外部依赖,可离线运行。
|
||||
"""
|
||||
|
||||
from paste.util.pagination import Pagination
|
||||
|
||||
|
||||
class TestPagination:
|
||||
"""分页功能测试"""
|
||||
|
||||
def test_pages_calculation_exact(self):
|
||||
"""精确整除的分页计算"""
|
||||
p = Pagination(row_count=100)
|
||||
pages = p.pages(page_size=20)
|
||||
assert pages == 5
|
||||
|
||||
def test_pages_calculation_remainder(self):
|
||||
"""有余数的分页计算"""
|
||||
p = Pagination(row_count=101)
|
||||
pages = p.pages(page_size=20)
|
||||
assert pages == 6
|
||||
|
||||
def test_pages_calculation_zero_rows(self):
|
||||
"""零行数据的处理"""
|
||||
p = Pagination(row_count=0)
|
||||
pages = p.pages(page_size=20)
|
||||
assert pages == 1
|
||||
|
||||
def test_page_number_valid(self):
|
||||
"""页码有效性检测"""
|
||||
p = Pagination(row_count=50)
|
||||
p.pages(page_size=20)
|
||||
assert p.number(1) == 1
|
||||
assert p.number(3) == 3 # 超出范围应返回最大页
|
||||
|
||||
def test_page_number_negative(self):
|
||||
"""负页码处理"""
|
||||
p = Pagination(row_count=50)
|
||||
p.pages(page_size=20)
|
||||
assert p.number(-1) == 1
|
||||
|
||||
def test_offset_calculation(self):
|
||||
"""偏移量计算"""
|
||||
p = Pagination(row_count=100)
|
||||
p.pages(page_size=20)
|
||||
assert p.offset(1) == 0
|
||||
assert p.offset(2) == 20
|
||||
assert p.offset(3) == 40
|
||||
|
||||
def test_paging_chain(self):
|
||||
"""链式调用分页"""
|
||||
p = Pagination(row_count=123).paging(page_number=2, page_size=20)
|
||||
assert p.page_count == 7
|
||||
assert p.page_number == 2
|
||||
assert p.offset_size == 20
|
||||
|
||||
def test_page_size_bounds(self):
|
||||
"""页大小边界: 最小1,最大1000"""
|
||||
p = Pagination(row_count=2000)
|
||||
assert p.pages(page_size=0) > 0
|
||||
assert p.pages(page_size=2000) <= 1000
|
||||
|
||||
def test_large_dataset(self):
|
||||
"""大数据集分页"""
|
||||
p = Pagination(row_count=1000000)
|
||||
pages = p.pages(page_size=100)
|
||||
assert pages == 10000
|
||||
|
||||
def test_single_row(self):
|
||||
"""单行数据"""
|
||||
p = Pagination(row_count=1)
|
||||
assert p.pages(page_size=10) == 1
|
||||
p.paging(page_number=1, page_size=10)
|
||||
assert p.page_number == 1
|
||||
assert p.offset_size == 0
|
||||
|
||||
def test_page_number_upper_bound(self):
|
||||
"""页码上限处理"""
|
||||
p = Pagination(row_count=30).paging(page_number=100, page_size=10)
|
||||
assert p.page_number == 3 # 最大只能到3页
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 以下是从 test_db.py 迁移过来的分页测试
|
||||
# 原函数 test_pagination() 改为标准的 pytest 测试
|
||||
# ============================================================
|
||||
|
||||
|
||||
class TestPaginationFromDb:
|
||||
"""从 test_db.py 迁移的分页测试"""
|
||||
|
||||
def test_pagination_basic(self):
|
||||
"""基础分页计算"""
|
||||
from paste.util.pagination import Pagination
|
||||
p = Pagination(row_count=123).paging(page_number=2, page_size=20)
|
||||
assert p.page_count == 7
|
||||
assert p.page_number == 2
|
||||
assert p.offset_size == 20
|
||||
|
||||
def test_pagination_first_page(self):
|
||||
"""第一页"""
|
||||
from paste.util.pagination import Pagination
|
||||
p = Pagination(row_count=50).paging(page_number=1, page_size=10)
|
||||
assert p.page_number == 1
|
||||
assert p.offset_size == 0
|
||||
|
||||
def test_pagination_last_page(self):
|
||||
"""最后一页"""
|
||||
from paste.util.pagination import Pagination
|
||||
p = Pagination(row_count=55).paging(page_number=6, page_size=10)
|
||||
assert p.page_number == 6
|
||||
assert p.offset_size == 50
|
||||
|
||||
def test_pagination_out_of_range(self):
|
||||
"""超出范围时自动修正"""
|
||||
from paste.util.pagination import Pagination
|
||||
p = Pagination(row_count=30).paging(page_number=100, page_size=10)
|
||||
assert p.page_number == 3 # 只有3页,自动修正
|
||||
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
测试 paste 包的基本导入和版本信息。
|
||||
无外部依赖,可离线运行。
|
||||
"""
|
||||
|
||||
import paste
|
||||
|
||||
|
||||
class TestPasteImport:
|
||||
"""测试 paste 包基础功能"""
|
||||
|
||||
def test_paste_imports(self):
|
||||
"""确保 paste 包能正确导入"""
|
||||
assert paste is not None
|
||||
|
||||
def test_paste_version(self):
|
||||
"""检查 paste 包是否有 __version__"""
|
||||
assert hasattr(paste, "__version__"), "paste package should have __version__"
|
||||
assert isinstance(paste.__version__, str), "__version__ should be a string"
|
||||
|
||||
def test_paste_version_value(self):
|
||||
"""验证版本号格式符合语义化版本规范"""
|
||||
import re
|
||||
version = paste.__version__
|
||||
assert re.match(r'^\d+\.\d+\.\d+', version), \
|
||||
f"Version {version} should follow semver format"
|
||||
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
测试雪花 ID 生成器。
|
||||
无外部依赖,可离线运行。
|
||||
"""
|
||||
|
||||
from paste.util.snow_id import IdWorker
|
||||
|
||||
|
||||
class TestSnowflakeId:
|
||||
"""雪花 ID 生成器测试"""
|
||||
|
||||
def test_snow_id_generates_string(self):
|
||||
"""测试 Snowflake ID 是否生成字符串"""
|
||||
id_worker = IdWorker.get_id_worker()
|
||||
sid = f'{id_worker.get_id()}'
|
||||
assert isinstance(sid, str), "雪花 ID 必须是字符串"
|
||||
assert len(sid) > 0, "雪花 ID 必须包含内容"
|
||||
|
||||
def test_snow_id_is_unique(self):
|
||||
"""测试生成的 ID 是否唯一(简单验证)"""
|
||||
id_worker = IdWorker.get_id_worker()
|
||||
ids = [id_worker.get_id() for _ in range(50)]
|
||||
assert len(set(ids)) == len(ids), "All generated IDs should be unique"
|
||||
|
||||
def test_snow_id_monotonic_increase(self):
|
||||
"""测试雪花 ID 单调递增"""
|
||||
id_worker = IdWorker.get_id_worker()
|
||||
ids = [id_worker.get_id() for _ in range(100)]
|
||||
for i in range(1, len(ids)):
|
||||
assert ids[i] > ids[i - 1], \
|
||||
f"ID at position {i} should be greater than previous"
|
||||
|
||||
def test_snow_id_worker_isolation(self):
|
||||
"""测试不同 worker 生成的 ID 不冲突"""
|
||||
worker1 = IdWorker.get_id_worker(datacenter_id=1, worker_id=1)
|
||||
worker2 = IdWorker.get_id_worker(datacenter_id=2, worker_id=2)
|
||||
ids = [worker1.get_id() for _ in range(50)] + \
|
||||
[worker2.get_id() for _ in range(50)]
|
||||
assert len(set(ids)) == len(ids), \
|
||||
"IDs from different workers should be unique"
|
||||
|
||||
def test_snow_id_high_throughput(self):
|
||||
"""测试短时间高并发生成"""
|
||||
id_worker = IdWorker.get_id_worker()
|
||||
ids = [id_worker.get_id() for _ in range(1000)]
|
||||
assert len(set(ids)) == 1000
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
测试字典工具。
|
||||
无外部依赖,可离线运行。
|
||||
"""
|
||||
|
||||
from paste.util import udict
|
||||
|
||||
|
||||
class TestUdict:
|
||||
"""字典工具测试"""
|
||||
|
||||
def test_get_by_path_simple(self):
|
||||
"""简单路径读取"""
|
||||
data = {"a": 1, "b": 2}
|
||||
assert udict.get_by_path(data, "a") == 1
|
||||
|
||||
def test_get_by_path_nested(self):
|
||||
"""嵌套路径读取"""
|
||||
data = {"a": {"b": {"c": 123}}}
|
||||
assert udict.get_by_path(data, "a.b.c") == 123
|
||||
|
||||
def test_get_by_path_missing(self):
|
||||
"""缺失路径处理"""
|
||||
data = {"a": 1}
|
||||
assert udict.get_by_path(data, "b.c.d", "default") == "default"
|
||||
|
||||
def test_get_by_path_none_default(self):
|
||||
"""缺失路径无默认值"""
|
||||
data = {"a": 1}
|
||||
assert udict.get_by_path(data, "b") is None
|
||||
|
||||
def test_get_with_default_existing(self):
|
||||
"""存在的键读取"""
|
||||
data = {"key": "value"}
|
||||
assert udict.get_with_default(data, "key", "fallback") == "value"
|
||||
|
||||
def test_get_with_default_missing(self):
|
||||
"""缺失键使用默认值"""
|
||||
data = {"key": "value"}
|
||||
assert udict.get_with_default(data, "missing", "fallback") == "fallback"
|
||||
|
||||
def test_get_with_default_none_value(self):
|
||||
"""值为 None 时使用默认值"""
|
||||
data = {"key": None}
|
||||
assert udict.get_with_default(data, "key", "fallback") == "fallback"
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
测试字符串工具。
|
||||
无外部依赖,可离线运行。
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from paste.util import ustr
|
||||
|
||||
|
||||
class TestUstr:
|
||||
"""字符串工具测试"""
|
||||
|
||||
def test_str_q_count_all_cn(self):
|
||||
"""全中文统计"""
|
||||
assert ustr.str_q_count("中国汉字") == 4
|
||||
|
||||
def test_str_q_count_mixed(self):
|
||||
"""中英文混合统计"""
|
||||
count = ustr.str_q_count("Hello中国")
|
||||
assert count == 2 # 只有中文字符算
|
||||
|
||||
def test_str_q_count_empty(self):
|
||||
"""空字符串统计"""
|
||||
assert ustr.str_q_count("") == 0
|
||||
|
||||
def test_str_q_count_no_cn(self):
|
||||
"""纯英文统计"""
|
||||
assert ustr.str_q_count("HelloWorld") == 0
|
||||
|
||||
def test_to_datetime_standard(self):
|
||||
"""标准格式解析"""
|
||||
result = ustr.to_datetime("2024-01-15 10:30:00", ["%Y-%m-%d %H:%M:%S"])
|
||||
assert result is not None
|
||||
assert isinstance(result, datetime.datetime)
|
||||
|
||||
def test_to_datetime_invalid(self):
|
||||
"""无效格式解析"""
|
||||
result = ustr.to_datetime("not-a-date", ["%Y-%m-%d"])
|
||||
assert result is None
|
||||
|
||||
def test_to_datetime_empty(self):
|
||||
"""空字符串解析"""
|
||||
result = ustr.to_datetime("", ["%Y-%m-%d"])
|
||||
assert result is None
|
||||
Reference in New Issue
Block a user