Files
gaokao-volunteer-system/data/orders/tests/test_schema.py

245 lines
7.2 KiB
Python
Raw Normal View History

"""schema 模块测试"""
import os
import sqlite3
import tempfile
from pathlib import Path
import pytest
os.environ.setdefault("GAOKAO_ORDERS_FERNET_KEY", "test-secret-for-unit-tests")
from data.orders.schema import apply_schema, get_schema_version, SCHEMA_SQL
@pytest.fixture
def tmp_db():
with tempfile.TemporaryDirectory() as d:
yield Path(d) / "test_orders.db"
def test_apply_schema_creates_orders_table(tmp_db):
conn = apply_schema(tmp_db)
try:
row = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='orders'"
).fetchone()
assert row is not None
finally:
conn.close()
def test_apply_schema_creates_status_history_table(tmp_db):
conn = apply_schema(tmp_db)
try:
row = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='order_status_history'"
).fetchone()
assert row is not None
finally:
conn.close()
def test_apply_schema_enables_foreign_keys(tmp_db):
conn = apply_schema(tmp_db)
try:
fk = conn.execute("PRAGMA foreign_keys").fetchone()[0]
assert fk == 1
finally:
conn.close()
def test_apply_schema_is_idempotent(tmp_db):
"""重复执行 apply_schema 不报错。"""
conn1 = apply_schema(tmp_db)
conn1.close()
conn2 = apply_schema(tmp_db)
conn2.close() # 不抛即通过
def test_apply_schema_creates_indexes(tmp_db):
conn = apply_schema(tmp_db)
try:
idxs = conn.execute(
"SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'"
).fetchall()
idx_names = {i[0] for i in idxs}
assert "idx_orders_status" in idx_names
assert "idx_orders_source" in idx_names
assert "idx_orders_created_at" in idx_names
assert "idx_orders_phone_hash" in idx_names
assert "idx_status_history_order" in idx_names
finally:
conn.close()
def test_apply_schema_creates_parent_dir(tmp_db):
"""父目录不存在时自动创建。"""
nested = tmp_db.parent / "subdir1" / "subdir2" / "nested.db"
assert not nested.parent.exists()
conn = apply_schema(nested)
conn.close()
assert nested.exists()
def test_check_constraint_rejects_invalid_status(tmp_db):
"""CHECK 约束拒绝非法 status 字符串。"""
conn = apply_schema(tmp_db)
try:
with pytest.raises(sqlite3.IntegrityError):
conn.execute(
"""INSERT INTO orders
(id, source, service_version, amount_cents, status,
status_updated_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(
"X1",
"web",
"basic",
100,
"INVALID_STATUS",
"2026-06-12T10:00:00+00:00",
"2026-06-12T10:00:00+00:00",
),
)
finally:
conn.close()
def test_check_constraint_rejects_negative_amount(tmp_db):
"""CHECK 约束拒绝负金额。"""
conn = apply_schema(tmp_db)
try:
with pytest.raises(sqlite3.IntegrityError):
conn.execute(
"""INSERT INTO orders
(id, source, service_version, amount_cents, status,
status_updated_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(
"X2",
"web",
"basic",
-1,
"pending",
"2026-06-12T10:00:00+00:00",
"2026-06-12T10:00:00+00:00",
),
)
finally:
conn.close()
def test_unique_external_id_per_source(tmp_db):
"""(source, external_id) 组合唯一。"""
conn = apply_schema(tmp_db)
try:
conn.execute(
"""INSERT INTO orders
(id, source, external_id, service_version, amount_cents, status,
status_updated_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
(
"A1",
"xianyu",
"EXT-001",
"basic",
100,
"pending",
"2026-06-12T10:00:00+00:00",
"2026-06-12T10:00:00+00:00",
),
)
with pytest.raises(sqlite3.IntegrityError):
conn.execute(
"""INSERT INTO orders
(id, source, external_id, service_version, amount_cents, status,
status_updated_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
(
"A2",
"xianyu",
"EXT-001",
"basic",
100,
"pending",
"2026-06-12T10:00:00+00:00",
"2026-06-12T10:00:00+00:00",
),
)
# 不同 source 可同 external_id
conn.execute(
"""INSERT INTO orders
(id, source, external_id, service_version, amount_cents, status,
status_updated_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
(
"A3",
"wechat",
"EXT-001",
"basic",
100,
"pending",
"2026-06-12T10:00:00+00:00",
"2026-06-12T10:00:00+00:00",
),
)
finally:
conn.close()
def test_status_history_cascade_delete(tmp_db):
"""删除订单时状态历史级联删除。"""
conn = apply_schema(tmp_db)
try:
conn.execute(
"""INSERT INTO orders
(id, source, service_version, amount_cents, status,
status_updated_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?)""",
(
"B1",
"web",
"basic",
100,
"pending",
"2026-06-12T10:00:00+00:00",
"2026-06-12T10:00:00+00:00",
),
)
conn.execute(
"""INSERT INTO order_status_history
(order_id, from_status, to_status, actor, changed_at)
VALUES (?, ?, ?, ?, ?)""",
("B1", None, "pending", "system", "2026-06-12T10:00:00+00:00"),
)
conn.commit()
# 验证存在
count = conn.execute(
"SELECT COUNT(*) FROM order_status_history WHERE order_id = ?", ("B1",)
).fetchone()[0]
assert count == 1
# 删除订单
conn.execute("DELETE FROM orders WHERE id = ?", ("B1",))
conn.commit()
# 级联删除
count = conn.execute(
"SELECT COUNT(*) FROM order_status_history WHERE order_id = ?", ("B1",)
).fetchone()[0]
assert count == 0
finally:
conn.close()
def test_get_schema_version_returns_1_after_apply(tmp_db):
conn = apply_schema(tmp_db)
try:
assert get_schema_version(conn) == 1
finally:
conn.close()
def test_schema_sql_is_nonempty():
assert "CREATE TABLE IF NOT EXISTS orders" in SCHEMA_SQL
assert "CREATE TABLE IF NOT EXISTS order_status_history" in SCHEMA_SQL