This commit is contained in:
macbook_max
2026-10-15 20:36:59 +08:00
commit 6bf949949c
7923 changed files with 1369582 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
"""测试基础设施:内存 SQLite(跳过索引)+ 内存 FakeRedis + 敏感词进程缓存复位"""
import pytest
from sqlalchemy import schema
from sqlalchemy.dialects import mysql
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.ext.compiler import compiles
import app.models # noqa: F401 注册全部模型
from app.models.base import Base
from app.services import sensitive_service
@compiles(mysql.BIGINT, "sqlite")
def _bigint_as_integer(element, compiler, **kw):
# MySQL BIGINT UNSIGNED 在 SQLite 渲染为 INTEGER,主键才能成为 rowid 别名(自增可用)
return "INTEGER"
@compiles(mysql.INTEGER, "sqlite")
def _int_as_integer(element, compiler, **kw):
return "INTEGER"
class FakeRedis:
"""内存版 Redis 桩:覆盖搜索/敏感词/配置/限流用到的命令子集"""
def __init__(self):
self.kv: dict[str, str] = {}
self.hashes: dict[str, dict[str, str]] = {}
self.zsets: dict[str, dict[str, float]] = {}
self.ttls: dict[str, int] = {}
async def get(self, key):
return self.kv.get(key)
async def set(self, key, value, ex=None, nx=False):
if nx and key in self.kv:
return None
self.kv[key] = str(value)
if ex is not None:
self.ttls[key] = ex
return True
async def setex(self, key, ex, value):
self.kv[key] = str(value)
self.ttls[key] = ex
return True
async def delete(self, *keys):
for key in keys:
self.kv.pop(key, None)
self.hashes.pop(key, None)
self.zsets.pop(key, None)
return len(keys)
async def expire(self, key, ttl):
self.ttls[key] = ttl
return True
async def incr(self, key):
self.kv[key] = str(int(self.kv.get(key, "0")) + 1)
return int(self.kv[key])
async def hset(self, key, mapping=None, **kw):
items = mapping or kw
self.hashes.setdefault(key, {}).update({str(k): str(v) for k, v in items.items()})
return len(items)
async def hgetall(self, key):
return dict(self.hashes.get(key, {}))
async def hget(self, key, field):
return self.hashes.get(key, {}).get(field)
async def zadd(self, key, mapping):
self.zsets.setdefault(key, {}).update({str(m): float(s) for m, s in mapping.items()})
return len(mapping)
async def zincrby(self, key, amount, member):
z = self.zsets.setdefault(key, {})
z[str(member)] = z.get(str(member), 0.0) + float(amount)
return z[str(member)]
async def zrevrange(self, key, start, stop):
items = sorted(self.zsets.get(key, {}).items(), key=lambda kv: kv[1], reverse=True)
members = [m for m, _ in items]
if stop < 0:
stop = len(members) + stop # Redis 负索引语义
return members[start:stop + 1]
@pytest.fixture
def redis():
return FakeRedis()
@pytest.fixture(autouse=True)
def _reset_sensitive_cache():
# 敏感词匹配器进程缓存 60s,测试间必须复位
sensitive_service._local_cache = None
yield
sensitive_service._local_cache = None
@pytest.fixture
async def db():
"""内存 SQLite 会话:只建表不建索引(模型存在跨表重名索引,SQLite 索引名全局唯一)"""
eng = create_async_engine("sqlite+aiosqlite:///:memory:")
async with eng.begin() as conn:
await conn.run_sync(lambda c: [c.execute(schema.CreateTable(t)) for t in Base.metadata.sorted_tables])
Session = async_sessionmaker(eng, expire_on_commit=False)
async with Session() as s:
yield s
await eng.dispose()