Files
item_bank/tests/test_questions.py
2026-08-01 16:50:55 +08:00

105 lines
3.7 KiB
Python

"""题目 CRUD 与跨用户越权隔离。"""
import pytest
CHOICE_Q = {
"type": "single_choice",
"stem_markdown": "求 $x^2-5x+6=0$ 的较大根",
"options": [
{"key": "A", "text_markdown": "$2$"},
{"key": "B", "text_markdown": "$3$"},
],
"correct_answer": ["B"],
"tags": ["代数", "一元二次方程"],
}
async def test_create_and_get(client, auth_headers):
h = await auth_headers()
r = await client.post("/api/questions", json=CHOICE_Q, headers=h)
assert r.status_code == 201
created = r.json()
assert created["type"] == "single_choice"
assert sorted(created["tags"]) == ["一元二次方程", "代数"]
# LaTeX 原样保留
assert "$x^2-5x+6=0$" in created["stem_markdown"]
r = await client.get(f"/api/questions/{created['id']}", headers=h)
assert r.status_code == 200
assert r.json()["correct_answer"] == ["B"]
async def test_update_replaces_tags(client, auth_headers):
h = await auth_headers()
qid = (await client.post("/api/questions", json=CHOICE_Q, headers=h)).json()["id"]
r = await client.put(
f"/api/questions/{qid}",
json={"tags": ["代数"], "difficulty": 3},
headers=h,
)
assert r.status_code == 200
assert r.json()["tags"] == ["代数"]
assert r.json()["difficulty"] == 3
async def test_delete(client, auth_headers):
h = await auth_headers()
qid = (await client.post("/api/questions", json=CHOICE_Q, headers=h)).json()["id"]
assert (await client.delete(f"/api/questions/{qid}", headers=h)).status_code == 204
assert (await client.get(f"/api/questions/{qid}", headers=h)).status_code == 404
@pytest.mark.parametrize(
"params,expected",
[
("?tag=代数", 1),
("?tag=不存在的标签", 0),
("?type=single_choice", 1),
("?type=multiple_choice", 0),
("?q=x^2", 1),
("?q=完全无关的关键词", 0),
],
)
async def test_list_filters(client, auth_headers, params, expected):
h = await auth_headers()
await client.post("/api/questions", json=CHOICE_Q, headers=h)
r = await client.get(f"/api/questions{params}", headers=h)
assert r.status_code == 200
assert r.json()["total"] == expected
class TestUserIsolation:
"""B 用户不得以任何方式接触 A 用户的题目(防 IDOR)。"""
async def test_cannot_read_list_or_item(self, client, auth_headers):
ha = await auth_headers("alice")
hb = await auth_headers("bob")
qid = (await client.post("/api/questions", json=CHOICE_Q, headers=ha)).json()["id"]
assert (await client.get("/api/questions", headers=hb)).json()["total"] == 0
assert (await client.get(f"/api/questions/{qid}", headers=hb)).status_code == 404
async def test_cannot_modify_or_delete(self, client, auth_headers):
ha = await auth_headers("alice")
hb = await auth_headers("bob")
qid = (await client.post("/api/questions", json=CHOICE_Q, headers=ha)).json()["id"]
assert (
await client.put(
f"/api/questions/{qid}", json={"difficulty": 5}, headers=hb
)
).status_code == 404
assert (
await client.delete(f"/api/questions/{qid}", headers=hb)
).status_code == 404
# 确认 A 的数据没被动过
assert (await client.get(f"/api/questions/{qid}", headers=ha)).status_code == 200
async def test_tags_are_isolated(self, client, auth_headers):
ha = await auth_headers("alice")
hb = await auth_headers("bob")
await client.post("/api/questions", json=CHOICE_Q, headers=ha)
assert (await client.get("/api/tags", headers=hb)).json() == []
assert len((await client.get("/api/tags", headers=ha)).json()) == 2