"""题目 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