Files
Hulumath-Web/backend/contest/test_game_services.py
T

178 lines
5.4 KiB
Python

import pytest
from rest_framework.exceptions import ValidationError
from accounts.models import User
from contest.game_services import (
SUDOKU_PUZZLES,
request_sudoku_hint,
start_game,
submit_game,
)
from contest.models import MathGameAttempt
@pytest.fixture
def game_user(db):
return User.objects.create_user(
username="game_user",
password="StrongPass_2026",
nickname="游戏玩家",
)
@pytest.mark.django_db
def test_twenty_four_服务端校验数字使用与幂等提交(game_user):
attempt = MathGameAttempt.objects.create(
user=game_user,
kind=MathGameAttempt.Kind.TWENTY_FOUR,
puzzle={"numbers": [1, 3, 4, 6]},
solution={"target": 24},
)
result = submit_game(
game_user,
attempt.id,
{"expression": "6 / (1 - 3 / 4)"},
"game-submit-1",
)
replay = submit_game(
game_user,
attempt.id,
{"expression": "1 + 3 + 4 + 6"},
"game-submit-1",
)
assert result["status"] == MathGameAttempt.Status.COMPLETED
assert result["score"] >= 100
assert replay["score"] == result["score"]
@pytest.mark.django_db
def test_twenty_four_api_兼容常见数学符号并完成计分(client, game_user):
attempt = MathGameAttempt.objects.create(
user=game_user,
kind=MathGameAttempt.Kind.TWENTY_FOUR,
puzzle={"numbers": [1, 3, 4, 6]},
solution={"target": 24},
)
client.force_login(game_user)
response = client.post(
f"/api/v1/contests/games/attempts/{attempt.id}/submit/",
{"expression": "6÷(1−3÷4)"},
content_type="application/json",
HTTP_IDEMPOTENCY_KEY="unicode-game-submit",
)
assert response.status_code == 200
assert response.json()["status"] == MathGameAttempt.Status.COMPLETED
assert response.json()["score"] >= 100
attempt.refresh_from_db()
assert attempt.submission == {"expression": "6/(1-3/4)"}
@pytest.mark.django_db
def test_twenty_four_api_答案错误时返回具体原因和请求编号(client, game_user):
attempt = MathGameAttempt.objects.create(
user=game_user,
kind=MathGameAttempt.Kind.TWENTY_FOUR,
puzzle={"numbers": [1, 3, 4, 6]},
solution={"target": 24},
)
client.force_login(game_user)
response = client.post(
f"/api/v1/contests/games/attempts/{attempt.id}/submit/",
{"expression": "1 + 3 + 4 + 6"},
content_type="application/json",
HTTP_IDEMPOTENCY_KEY="incorrect-game-submit",
HTTP_X_REQUEST_ID="twenty-four-invalid-test",
)
assert response.status_code == 400
assert response.json()["error"]["message"] == "当前结果是 14,还没有得到 24"
assert response.json()["error"]["request_id"] == "twenty-four-invalid-test"
attempt.refresh_from_db()
assert attempt.status == MathGameAttempt.Status.ACTIVE
@pytest.mark.django_db
def test_twenty_four_拒绝额外数字和非四则表达式(game_user):
attempt = MathGameAttempt.objects.create(
user=game_user,
kind=MathGameAttempt.Kind.TWENTY_FOUR,
puzzle={"numbers": [1, 3, 4, 6]},
solution={"target": 24},
)
with pytest.raises(ValidationError, match="四个数字"):
submit_game(
game_user,
attempt.id,
{"expression": "6 / (1 - 3 / 4) + 24 - 24"},
"invalid-extra-number",
)
with pytest.raises(ValidationError, match="只允许"):
submit_game(
game_user,
attempt.id,
{"expression": "pow(2, 3) * 3"},
"invalid-function",
)
with pytest.raises(ValidationError, match="只允许"):
submit_game(
game_user,
attempt.id,
{"expression": "6 / (True - 3 / 4)"},
"invalid-boolean",
)
@pytest.mark.django_db
def test_sudoku_提示受限并由服务端校验完整答案(game_user):
payload = start_game(
game_user,
MathGameAttempt.Kind.SUDOKU,
MathGameAttempt.Difficulty.EASY,
)
attempt = MathGameAttempt.objects.get(id=payload["attempt_id"])
hint = request_sudoku_hint(game_user, attempt.id)
solution_text = SUDOKU_PUZZLES[MathGameAttempt.Difficulty.EASY][0][1]
solution = [
[int(solution_text[row * 9 + column]) for column in range(9)]
for row in range(9)
]
result = submit_game(
game_user,
attempt.id,
{"grid": solution},
"sudoku-submit-1",
)
assert hint["value"] == solution[hint["row"]][hint["column"]]
assert result["status"] == MathGameAttempt.Status.COMPLETED
assert result["hints_used"] == 1
@pytest.mark.django_db
def test_math_game_api_目录公开但开局需要登录(client, game_user):
catalog = client.get("/api/v1/contests/games/")
anonymous_start = client.post(
"/api/v1/contests/games/twenty_four/start/",
{"difficulty": "easy"},
content_type="application/json",
)
client.force_login(game_user)
authenticated_start = client.post(
"/api/v1/contests/games/twenty_four/start/",
{"difficulty": "easy"},
content_type="application/json",
)
assert catalog.status_code == 200
assert {item["kind"] for item in catalog.json()} == {"sudoku", "twenty_four"}
assert anonymous_start.status_code in {401, 403}
assert authenticated_start.status_code == 201
assert "solution" not in authenticated_start.json()