Files
Hulumath-Web/backend/math_life/test_services.py
T
Jacky 1f98f7152d
CI / test (push) Failing after 1m31s
v0.2 Preview: Construction Plan.
2026-08-08 21:25:27 +08:00

145 lines
4.3 KiB
Python

import pytest
from rest_framework.exceptions import ValidationError
from accounts.models import User
from math_life.models import Story, StoryChoice, StoryRun, StoryVersion
from math_life.services import (
make_choice,
score_mathbti,
start_story,
validate_story_content,
)
@pytest.fixture
def mathbti_definition():
return {
"axes": [{"id": "style"}, {"id": "purpose"}],
"scoring": {"axes": ["style", "purpose"], "cutoff": 1},
"questions": [
{
"id": 1,
"axis": "style",
"options": [{"score": 0}, {"score": 2}],
},
{
"id": 2,
"axis": "purpose",
"options": [{"score": 0}, {"score": 2}],
},
],
}
def test_score_mathbti_按轴汇总并生成身份编码(mathbti_definition):
code, scores = score_mathbti(mathbti_definition, [1, 0])
assert code == "10"
assert scores == {"style": 2, "purpose": 0}
def test_score_mathbti_答案数量不一致时拒绝(mathbti_definition):
with pytest.raises(ValidationError, match="答案数量"):
score_mathbti(mathbti_definition, [1])
def test_score_mathbti_负数选项索引必须拒绝(mathbti_definition):
with pytest.raises(ValidationError, match="答案无效"):
score_mathbti(mathbti_definition, [-1, 0])
def test_validate_story_content_报告缺失引用与不可达节点():
document = {
"start_node": "start",
"nodes": {
"start": {"choices": [{"text": "错误入口", "next": "missing"}]},
"orphan": {"choices": []},
},
}
errors = validate_story_content(document)
assert any("不存在的节点 missing" in error for error in errors)
assert any("不可达节点: orphan" in error for error in errors)
@pytest.fixture
def story_setup(db):
user = User.objects.create_user(
username="story_user",
password="StrongPass_2026",
nickname="剧情用户",
)
story = Story.objects.create(slug="test-story", title="测试人生")
version = StoryVersion.objects.create(
story=story,
version=1,
is_published=True,
content={
"start_node": "start",
"nodes": {
"start": {
"scene": "起点",
"choices": [
{
"text": "向左",
"next": "left_end",
"effects": {"energy": -1, "favorability": {"高斯": 2}},
},
{
"text": "向右",
"next": "right_end",
"effects": {"energy": 2},
},
],
},
"left_end": {"scene": "左结局", "choices": []},
"right_end": {"scene": "右结局", "choices": []},
},
},
)
return user, story, version
@pytest.mark.django_db
def test_make_choice_应用嵌套效果完成结局且幂等(story_setup):
user, story, _ = story_setup
started = start_story(user, story)
result = make_choice(
run_id=started["run_id"],
user=user,
choice_index=0,
idempotency_key="choice-1",
)
replay = make_choice(
run_id=started["run_id"],
user=user,
choice_index=0,
idempotency_key="choice-1",
)
assert result["status"] == StoryRun.Status.COMPLETED
assert result["current_node"] == "left_end"
assert result["state"] == {"energy": -1, "favorability": {"高斯": 2}}
assert replay["current_node"] == "left_end"
assert StoryChoice.objects.count() == 1
@pytest.mark.django_db
def test_make_choice_负数索引必须拒绝且存档不变(story_setup):
user, story, _ = story_setup
started = start_story(user, story)
with pytest.raises(ValidationError, match="选项不存在"):
make_choice(
run_id=started["run_id"],
user=user,
choice_index=-1,
idempotency_key="invalid-choice",
)
run = StoryRun.objects.get(id=started["run_id"])
assert run.current_node == "start"
assert StoryChoice.objects.count() == 0