fix: harden twenty four game submissions
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import ast
|
||||
import secrets
|
||||
import unicodedata
|
||||
from collections import Counter
|
||||
from fractions import Fraction
|
||||
|
||||
@@ -36,6 +37,18 @@ TWENTY_FOUR_PUZZLES = {
|
||||
MathGameAttempt.Difficulty.HARD: [(1, 5, 5, 5), (3, 3, 7, 7), (5, 5, 7, 11)],
|
||||
}
|
||||
|
||||
TWENTY_FOUR_SYMBOLS = str.maketrans(
|
||||
{
|
||||
"×": "*",
|
||||
"·": "*",
|
||||
"∙": "*",
|
||||
"÷": "/",
|
||||
"−": "-",
|
||||
"–": "-",
|
||||
"—": "-",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _grid_from_text(value):
|
||||
return [[int(value[row * 9 + column]) for column in range(9)] for row in range(9)]
|
||||
@@ -80,7 +93,10 @@ def start_game(user, kind, difficulty):
|
||||
|
||||
|
||||
def _validate_twenty_four_expression(source, numbers):
|
||||
source = str(source or "").strip()
|
||||
source = unicodedata.normalize("NFKC", str(source or "")).translate(
|
||||
TWENTY_FOUR_SYMBOLS
|
||||
)
|
||||
source = source.strip()
|
||||
if not source or len(source) > 120:
|
||||
raise ValidationError({"expression": "请输入不超过 120 个字符的表达式"})
|
||||
try:
|
||||
@@ -90,7 +106,11 @@ def _validate_twenty_four_expression(source, numbers):
|
||||
used = []
|
||||
|
||||
def evaluate(node):
|
||||
if isinstance(node, ast.Constant) and isinstance(node.value, int):
|
||||
if (
|
||||
isinstance(node, ast.Constant)
|
||||
and isinstance(node.value, int)
|
||||
and not isinstance(node.value, bool)
|
||||
):
|
||||
used.append(node.value)
|
||||
return Fraction(node.value)
|
||||
if isinstance(node, ast.BinOp) and isinstance(
|
||||
|
||||
@@ -47,6 +47,55 @@ def test_twenty_four_服务端校验数字使用与幂等提交(game_user):
|
||||
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(
|
||||
@@ -70,6 +119,13 @@ def test_twenty_four_拒绝额外数字和非四则表达式(game_user):
|
||||
{"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
|
||||
|
||||
Reference in New Issue
Block a user