105 lines
3.7 KiB
Python
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
|