Files
re-xxm/tests/test_database.py
T
kairee d971e718e9 feat(p3): add discovery, telegram notifications, and one-click favorites
Implement WBI video search with legacy fallback, classification scoring, candidate pool, permanent seen-aid dedupe, blacklist filtering, and batched Telegram notifications. Add write client for fav deal/sort, target-folder selection, insertion position calculation, read-back verification, fav_action locking, retry on failure, ignore and blacklist actions. Wire discovery, candidate cleanup, Telegram long polling, and new discover/candidates CLI commands into the scheduler. Add migration v3 for candidate duration and index stats.
2026-10-31 21:23:12 +08:00

143 lines
4.5 KiB
Python

# -*- coding: utf-8 -*-
"""数据库单写队列测试(不联网): uv run python tests/test_database.py"""
import os
import sqlite3
import sys
import tempfile
import threading
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.stdout.reconfigure(encoding="utf-8")
from reaction_board.database import Database, DatabaseClosed # noqa: E402
def make_db(tmp):
db = Database(Path(tmp) / "app.db", backup_dir=Path(tmp) / "backup")
db.initialize()
return db
def test_write_read_transaction():
with tempfile.TemporaryDirectory() as tmp:
db = make_db(tmp)
try:
assert db.schema_version == 3
db.write(lambda c: c.execute("INSERT INTO kv(k, v) VALUES ('a', '1')"))
assert db.read(lambda c: c.execute("SELECT v FROM kv WHERE k='a'").fetchone()[0]) == "1"
def boom(conn):
conn.execute("UPDATE kv SET v='2' WHERE k='a'")
raise RuntimeError("boom")
try:
db.transaction(boom)
except RuntimeError as exc:
assert str(exc) == "boom"
else:
raise AssertionError("事务应抛错")
assert db.read(lambda c: c.execute("SELECT v FROM kv WHERE k='a'").fetchone()[0]) == "1"
finally:
db.close()
def test_concurrent_writes_are_serialized():
with tempfile.TemporaryDirectory() as tmp:
db = make_db(tmp)
try:
db.write(lambda c: c.execute(
"CREATE TABLE counter (id INTEGER PRIMARY KEY, n INTEGER NOT NULL)"
))
db.write(lambda c: c.execute("INSERT INTO counter(id, n) VALUES (1, 0)"))
errors = []
def worker():
try:
for _ in range(10):
db.transaction(
lambda conn: conn.execute(
"UPDATE counter SET n = n + 1 WHERE id = 1"
)
)
except Exception as exc: # noqa: BLE001
errors.append(exc)
threads = [threading.Thread(target=worker) for _ in range(8)]
for t in threads:
t.start()
for t in threads:
t.join()
assert not errors, errors
assert db.read(lambda c: c.execute("SELECT n FROM counter").fetchone()[0]) == 80
finally:
db.close()
def test_close_and_reinitialize():
with tempfile.TemporaryDirectory() as tmp:
db = make_db(tmp)
db.close()
db.close() # 幂等
try:
db.write(lambda c: None)
except DatabaseClosed:
pass
else:
raise AssertionError("关闭后写入应失败")
try:
db.read(lambda c: None)
except DatabaseClosed:
pass
else:
raise AssertionError("关闭后读取应失败")
db2 = make_db(tmp)
try:
assert db2.schema_version == 3
assert db2.read(lambda c: c.execute("SELECT MAX(version) FROM schema_version").fetchone()[0]) == 3
finally:
db2.close()
def test_wal_enabled():
with tempfile.TemporaryDirectory() as tmp:
db = make_db(tmp)
try:
mode = db.read(lambda c: c.execute("PRAGMA journal_mode").fetchone()[0])
assert str(mode).lower() == "wal"
assert db.read(lambda c: c.execute("PRAGMA foreign_keys").fetchone()[0]) == 1
finally:
db.close()
def test_migration_backup_on_existing_db():
"""已有数据升级时由 Database 自动备份。"""
with tempfile.TemporaryDirectory() as tmp:
path = Path(tmp) / "app.db"
# 先建一个旧库(只有 schema_version v0),Database.initialize 会升级并备份
conn = sqlite3.connect(path)
conn.execute("CREATE TABLE legacy (x INTEGER)")
conn.commit()
conn.close()
db = Database(path, backup_dir=Path(tmp) / "backup")
try:
applied = db.initialize()
assert applied == [1, 2, 3]
assert len(list((Path(tmp) / "backup").glob("app-pre-v0-*.db"))) == 1
finally:
db.close()
def main():
test_write_read_transaction()
test_concurrent_writes_are_serialized()
test_close_and_reinitialize()
test_wal_enabled()
test_migration_backup_on_existing_db()
print("OK: 数据库单写队列与迁移入口测试全部通过")
if __name__ == "__main__":
main()