feat: add math engine and puzzle services
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
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_拒绝额外数字和非四则表达式(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",
|
||||
)
|
||||
|
||||
|
||||
@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()
|
||||
Reference in New Issue
Block a user