515 lines
26 KiB
Python
515 lines
26 KiB
Python
import base64
|
||
|
||
from fastapi.testclient import TestClient
|
||
from uuid import uuid4
|
||
|
||
import app.main as main_module
|
||
from app.main import app
|
||
from app.config import settings
|
||
|
||
|
||
def fake_ask_questions(payload, count, reference_text):
|
||
assert "shop_name" not in payload
|
||
return [f"本地测试问题{i + 1}?" for i in range(count)]
|
||
|
||
|
||
def test_admin_login_me_and_logout():
|
||
with TestClient(app) as client:
|
||
login = client.post("/api/admin/auth/login", json={"username": "admin", "password": "admin123456"})
|
||
assert login.status_code == 200
|
||
assert login.json()["data"]["user"]["role_code"] == "super_admin"
|
||
assert client.get("/api/admin/auth/me").status_code == 200
|
||
assert client.get("/api/admin/auth/login-logs").status_code == 200
|
||
assert client.post("/api/admin/auth/logout").status_code == 200
|
||
assert client.get("/api/admin/auth/me").status_code == 401
|
||
|
||
|
||
def test_wrong_password_is_rejected():
|
||
with TestClient(app) as client:
|
||
response = client.post("/api/admin/auth/login", json={"username": "admin", "password": "wrong"})
|
||
assert response.status_code == 401
|
||
|
||
|
||
def test_operator_cannot_access_super_admin_apis():
|
||
with TestClient(app) as client:
|
||
login = client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"})
|
||
assert login.status_code == 200
|
||
assert client.get("/api/admin/auth/login-logs").status_code == 403
|
||
assert client.get("/api/admin/users").status_code == 403
|
||
assert client.get("/api/admin/settings").status_code == 403
|
||
|
||
|
||
def test_register_and_super_admin_user_crud():
|
||
username = f"tester_{uuid4().hex[:8]}"
|
||
with TestClient(app) as client:
|
||
registration = client.post("/api/admin/auth/register", json={"username": username, "password": "password123", "display_name": "测试账号"})
|
||
assert registration.status_code == 201
|
||
assert registration.json()["data"]["user"]["role_code"] == "operator"
|
||
assert client.post("/api/admin/auth/login", json={"username": "admin", "password": "admin123456"}).status_code == 200
|
||
records = client.get("/api/admin/users").json()["data"]
|
||
created = next(item for item in records if item["username"] == username)
|
||
assert client.get(f"/api/admin/users/{created['id']}").status_code == 200
|
||
update = client.patch(f"/api/admin/users/{created['id']}", json={"display_name": "已修改账号", "is_active": False})
|
||
assert update.status_code == 200
|
||
assert client.delete(f"/api/admin/users/{created['id']}").status_code == 200
|
||
assert client.get(f"/api/admin/users/{created['id']}").status_code == 404
|
||
|
||
|
||
def test_qa_generation_requires_product_and_model_key():
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/qa/generate", json={"question_count": 2}).status_code == 401
|
||
login = client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"})
|
||
assert login.status_code == 200
|
||
assert client.post("/api/admin/qa/generate", json={"question_count": 2}).status_code == 400
|
||
|
||
|
||
def test_accepted_qa_is_saved_to_library_without_duplicates():
|
||
unique_product = f"问答库测试商品-{uuid4().hex[:8]}"
|
||
payload = {
|
||
"product_name": unique_product,
|
||
"category": "测试品类",
|
||
"benefits": "测试功效",
|
||
"selling_points": "测试卖点",
|
||
"items": [{
|
||
"question": "这款商品适合什么人群?",
|
||
"answers": ["适合关注日常使用体验的人群。", "建议结合自身需求选择。"],
|
||
"source": "AI生成",
|
||
}],
|
||
}
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
created = client.post("/api/admin/qa-library", json=payload)
|
||
assert created.status_code == 200
|
||
assert created.json()["data"]["added_count"] == 1
|
||
duplicate = client.post("/api/admin/qa-library", json=payload)
|
||
assert duplicate.status_code == 200
|
||
assert duplicate.json()["data"]["added_count"] == 0
|
||
assert duplicate.json()["data"]["skipped_count"] == 1
|
||
records = client.get(f"/api/admin/qa-library?search={unique_product}")
|
||
assert records.status_code == 200
|
||
assert len(records.json()["data"]) == 1
|
||
assert records.json()["data"][0]["answers"] == payload["items"][0]["answers"]
|
||
entry_id = records.json()["data"][0]["id"]
|
||
edited = client.patch(
|
||
f"/api/admin/qa-library/{entry_id}",
|
||
json={
|
||
**payload,
|
||
"question": "编辑后的问题?",
|
||
"answers": ["编辑后的参考回答。"],
|
||
},
|
||
)
|
||
assert edited.status_code == 200
|
||
assert edited.json()["data"]["question"] == "编辑后的问题?"
|
||
assert edited.json()["data"]["answers"] == ["编辑后的参考回答。"]
|
||
|
||
|
||
def test_task_board_requires_login():
|
||
with TestClient(app) as client:
|
||
assert client.get("/api/admin/task-board").status_code == 401
|
||
assert client.get("/api/admin/task-board/999999").status_code == 401
|
||
assert client.post("/api/admin/task-board/999999/cancel").status_code == 401
|
||
|
||
|
||
def test_task_board_is_available_to_operator():
|
||
with TestClient(app) as client:
|
||
login = client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"})
|
||
assert login.status_code == 200
|
||
response = client.get("/api/admin/task-board")
|
||
assert response.status_code == 200
|
||
assert isinstance(response.json()["data"], list)
|
||
counts = client.get("/api/admin/review-pending-count")
|
||
assert counts.status_code == 200
|
||
count_data = counts.json()["data"]
|
||
assert count_data["total"] == count_data["ask"] + count_data["answer"]
|
||
|
||
|
||
def test_answer_review_rejection_requires_reason():
|
||
with TestClient(app) as client:
|
||
login = client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"})
|
||
assert login.status_code == 200
|
||
missing = client.post("/api/admin/answer-completion-reviews/999999/decision", json={"approved": False})
|
||
assert missing.status_code == 404
|
||
|
||
|
||
def test_answer_rejection_reason_syncs_to_public_user():
|
||
previous_key = settings.llm_api_key
|
||
settings.llm_api_key = ""
|
||
phone = f"19{uuid4().int % 1_000_000_000:09d}"
|
||
try:
|
||
with TestClient(app) as client:
|
||
login = client.post("/api/answer/auth/phone-login", json={"phone": phone})
|
||
assert login.status_code == 200
|
||
token = login.json()["data"]["token"]
|
||
created = client.post(
|
||
"/api/answer/me/submissions",
|
||
data={"token": token},
|
||
files=[("files", ("invited.png", b"test-image", "image/png"))],
|
||
)
|
||
assert created.status_code == 200
|
||
submission = created.json()["data"]
|
||
question_id = submission["questions"][0]["id"]
|
||
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
assert client.patch(f"/api/admin/answer-task-drafts/questions/{question_id}", json={"selected": True}).status_code == 200
|
||
assert client.post(f"/api/admin/answer-task-drafts/{submission['id']}/publish").status_code == 200
|
||
completed = client.post(
|
||
f"/api/answer/me/submissions/{submission['id']}/completion",
|
||
data={"token": token},
|
||
files=[("files", ("done.png", b"done-image", "image/png"))],
|
||
)
|
||
assert completed.status_code == 200
|
||
no_reason = client.post(f"/api/admin/answer-completion-reviews/{submission['id']}/decision", json={"approved": False})
|
||
assert no_reason.status_code == 400
|
||
rejected = client.post(f"/api/admin/answer-completion-reviews/{submission['id']}/decision", json={"approved": False, "reason_code": "数量不足", "reason_text": "应完成1条回答,截图未完整展示"})
|
||
assert rejected.status_code == 200
|
||
current = client.get(f"/api/answer/me/submissions?token={token}").json()["data"][0]
|
||
assert current["status"] == "未通过"
|
||
assert current["latest_review"]["reason_code"] == "数量不足"
|
||
assert "截图未完整展示" in current["latest_review"]["reason_text"]
|
||
finally:
|
||
settings.llm_api_key = previous_key
|
||
|
||
|
||
def test_ask_task_publish_appears_on_task_board(monkeypatch):
|
||
monkeypatch.setattr(main_module, "generate_questions", fake_ask_questions)
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
missing_link = client.post("/api/admin/ask-tasks/generate", json={"product_name": "测试商品"})
|
||
assert missing_link.status_code == 400
|
||
missing_product_access = client.post(
|
||
"/api/admin/ask-tasks/generate",
|
||
json={"shop_name": "测试店铺", "product_name": "测试商品", "question_count": 3},
|
||
)
|
||
assert missing_product_access.status_code == 400
|
||
missing_shop = client.post("/api/admin/ask-tasks/generate", json={"product_link": "https://item.example.com/1", "product_name": "测试商品", "question_count": 3})
|
||
assert missing_shop.status_code == 400
|
||
generated = client.post("/api/admin/ask-tasks/generate", json={"shop_name": "测试店铺", "product_link": "https://item.example.com/1", "product_name": "测试商品", "question_count": 3})
|
||
assert generated.status_code == 200
|
||
task = generated.json()["data"]
|
||
assert task["public_token"] is None
|
||
assert task["shop_name"] == "测试店铺"
|
||
published = client.post(f"/api/admin/ask-tasks/{task['id']}/publish")
|
||
assert published.status_code == 200
|
||
public_token = published.json()["data"]["public_token"]
|
||
assert public_token
|
||
assert public_token != task["task_id"]
|
||
assert len(public_token) >= 24
|
||
board = client.get("/api/admin/task-board").json()["data"]
|
||
created = next(item for item in board if item["task_id"] == task["task_id"])
|
||
assert created["task_type"] == "ask"
|
||
assert created["task_type_label"] == "提问任务"
|
||
assert created["qa_count"] == 3
|
||
assert created["public_token"] == public_token
|
||
assert created["shop_name"] == "测试店铺"
|
||
detail = client.get(created["detail_endpoint"])
|
||
assert detail.status_code == 200
|
||
assert detail.json()["data"]["shop_name"] == "测试店铺"
|
||
|
||
|
||
def test_ask_task_generation_honors_question_count(monkeypatch):
|
||
monkeypatch.setattr(main_module, "generate_questions", fake_ask_questions)
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
generated = client.post(
|
||
"/api/admin/ask-tasks/generate",
|
||
json={
|
||
"shop_name": "数量测试店铺",
|
||
"product_link": "https://item.example.com/count-test",
|
||
"product_name": "数量测试商品",
|
||
"question_count": 25,
|
||
},
|
||
)
|
||
assert generated.status_code == 200
|
||
questions = generated.json()["data"]["selected_questions"]
|
||
assert len(questions) == 25
|
||
assert len({item["question"] for item in questions}) == 25
|
||
|
||
|
||
def test_ask_task_accepts_qr_image_without_token_and_excludes_it_from_ai(monkeypatch):
|
||
monkeypatch.setattr(main_module, "generate_questions", fake_ask_questions)
|
||
qr_data_url = "data:image/png;base64," + base64.b64encode(b"local-qr-image").decode()
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
generated = client.post(
|
||
"/api/admin/ask-tasks/generate",
|
||
json={
|
||
"shop_name": "二维码测试店铺",
|
||
"product_link": "",
|
||
"product_qr_image": qr_data_url,
|
||
"product_qr_image_name": "商品二维码.png",
|
||
"product_name": "二维码测试商品",
|
||
"question_count": 2,
|
||
},
|
||
)
|
||
assert generated.status_code == 200
|
||
task = generated.json()["data"]
|
||
assert task["product_link"] == ""
|
||
assert task["product_qr"]["content_type"] == "image/png"
|
||
assert task["product_qr"]["name"] == "商品二维码.png"
|
||
assert task["product_qr"]["data_url"] == qr_data_url
|
||
published = client.post(f"/api/admin/ask-tasks/{task['id']}/publish")
|
||
assert published.status_code == 200
|
||
assert published.json()["data"]["status"] == "已发布"
|
||
board = client.get("/api/admin/task-board").json()["data"]
|
||
created = next(item for item in board if item["task_id"] == task["task_id"])
|
||
detail = client.get(created["detail_endpoint"])
|
||
assert detail.status_code == 200
|
||
assert detail.json()["data"]["product_qr"]["data_url"] == qr_data_url
|
||
public_token = published.json()["data"]["public_token"]
|
||
assert public_token
|
||
|
||
|
||
def test_ask_task_regeneration_appends_to_existing_questions(monkeypatch):
|
||
monkeypatch.setattr(main_module, "generate_questions", fake_ask_questions)
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
first = client.post("/api/admin/ask-tasks/generate", json={"shop_name": "追加测试店铺", "product_link": "https://item.example.com/append", "product_name": "追加测试商品", "question_count": 2})
|
||
assert first.status_code == 200
|
||
task = first.json()["data"]
|
||
assert len(task["selected_questions"]) == 2
|
||
second = client.post("/api/admin/ask-tasks/generate", json={"task_id": task["id"], "shop_name": "追加测试店铺", "product_link": "https://item.example.com/append", "product_name": "追加测试商品", "question_count": 2})
|
||
assert second.status_code == 200
|
||
questions = second.json()["data"]["selected_questions"]
|
||
assert len(questions) == 4
|
||
assert [item["sequence_no"] for item in questions] == [1, 2, 3, 4]
|
||
|
||
|
||
def test_ask_task_added_question_is_idempotent_and_deletable(monkeypatch):
|
||
monkeypatch.setattr(main_module, "generate_questions", fake_ask_questions)
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
created = client.post(
|
||
"/api/admin/ask-tasks/generate",
|
||
json={
|
||
"shop_name": f"去重测试店铺-{uuid4().hex[:8]}",
|
||
"product_link": "TOKEN-IDEMPOTENT",
|
||
"product_name": "去重测试商品",
|
||
"question_count": 1,
|
||
},
|
||
)
|
||
assert created.status_code == 200
|
||
task_id = created.json()["data"]["id"]
|
||
first = client.post(f"/api/admin/ask-tasks/{task_id}/questions", json={"question": "问答库重复问题?", "source": "问答库"})
|
||
second = client.post(f"/api/admin/ask-tasks/{task_id}/questions", json={"question": "问答库重复问题?", "source": "问答库"})
|
||
assert first.status_code == 200 and second.status_code == 200
|
||
assert first.json()["data"]["id"] == second.json()["data"]["id"]
|
||
assert second.json()["data"]["source"] == "问答库"
|
||
question_id = first.json()["data"]["id"]
|
||
removed = client.delete(f"/api/admin/ask-tasks/{task_id}/questions/{question_id}")
|
||
assert removed.status_code == 200
|
||
refreshed = client.post(
|
||
"/api/admin/ask-tasks/generate",
|
||
json={
|
||
"task_id": task_id,
|
||
"shop_name": "去重测试店铺",
|
||
"product_link": "TOKEN-IDEMPOTENT",
|
||
"product_name": "去重测试商品",
|
||
"question_count": 1,
|
||
},
|
||
)
|
||
assert refreshed.status_code == 200
|
||
assert all(item["question"] != "问答库重复问题?" for item in refreshed.json()["data"]["selected_questions"])
|
||
|
||
|
||
def test_public_ask_user_gets_stable_unique_five_question_assignment():
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
generated = client.post(
|
||
"/api/admin/ask-tasks/generate",
|
||
json={
|
||
"shop_name": "公共提问测试店铺",
|
||
"product_link": "https://item.example.com/public-ask",
|
||
"product_name": "公共提问测试商品",
|
||
"question_count": 8,
|
||
},
|
||
).json()["data"]
|
||
assert client.post(f"/api/admin/ask-tasks/{generated['id']}/publish").status_code == 200
|
||
|
||
phone = f"139{uuid4().int % 100_000_000:08d}"
|
||
session = client.post("/api/ask/auth/phone-login", json={"phone": phone}).json()["data"]
|
||
url = f"/api/ask/tasks/{generated['task_id']}?token={session['token']}"
|
||
first = client.get(url)
|
||
second = client.get(url)
|
||
assert first.status_code == 200
|
||
assert second.status_code == 200
|
||
first_questions = first.json()["data"]["questions"]
|
||
second_questions = second.json()["data"]["questions"]
|
||
assert len(first_questions) == 5
|
||
assert len({item["id"] for item in first_questions}) == 5
|
||
assert [item["id"] for item in first_questions] == [item["id"] for item in second_questions]
|
||
|
||
no_selection = client.post(
|
||
f"/api/ask/tasks/{generated['task_id']}/selection",
|
||
json={"token": session["token"], "question_ids": []},
|
||
)
|
||
assert no_selection.status_code == 400
|
||
chosen = [first_questions[0]["id"]]
|
||
selected = client.post(
|
||
f"/api/ask/tasks/{generated['task_id']}/selection",
|
||
json={"token": session["token"], "question_ids": chosen},
|
||
)
|
||
assert selected.status_code == 200
|
||
assert selected.json()["data"]["status"] == "进行中"
|
||
assert [item["id"] for item in selected.json()["data"]["questions"] if item["selected"]] == chosen
|
||
|
||
submitted = client.post(
|
||
f"/api/ask/tasks/{generated['task_id']}/submission",
|
||
data={"token": session["token"], "payee_name": "测试用户"},
|
||
files=[
|
||
("files", ("question.png", b"question-image", "image/png")),
|
||
("payment_file", ("payment.png", b"payment-image", "image/png")),
|
||
],
|
||
)
|
||
assert submitted.status_code == 200
|
||
assert submitted.json()["data"]["status"] == "待审核"
|
||
assert len(submitted.json()["data"]["question_screenshots"]) == 1
|
||
assert len(submitted.json()["data"]["payment_screenshots"]) == 1
|
||
|
||
reviews = client.get("/api/admin/ask-completion-reviews")
|
||
assert reviews.status_code == 200
|
||
review = next(item for item in reviews.json()["data"] if item["assignment_id"] == submitted.json()["data"]["assignment_id"])
|
||
assert review["status"] == "待审核"
|
||
decision = client.post(
|
||
f"/api/admin/ask-completion-reviews/{review['assignment_id']}/decision",
|
||
json={"approved": True},
|
||
)
|
||
assert decision.status_code == 200
|
||
assert decision.json()["data"]["status"] == "已通过"
|
||
board = client.get("/api/admin/task-board").json()["data"]
|
||
payout = next(
|
||
item
|
||
for item in board
|
||
if item.get("record_kind") == "ask_batch"
|
||
and item["id"] == review["assignment_id"]
|
||
)
|
||
assert payout["status_label"] == "待返款"
|
||
assert payout["task_type"] == "ask"
|
||
assert payout["qa_count"] == 1
|
||
assert payout["completed_count"] == 0
|
||
payout_detail = client.get(payout["detail_endpoint"])
|
||
assert payout_detail.status_code == 200
|
||
assert len(payout_detail.json()["data"]["question_screenshots"]) == 1
|
||
assert len(payout_detail.json()["data"]["payment_screenshots"]) == 1
|
||
refunded = client.post(payout["refund_endpoint"])
|
||
assert refunded.status_code == 200
|
||
assert refunded.json()["data"]["status_label"] == "已完成"
|
||
assert refunded.json()["data"]["refund_endpoint"] is None
|
||
board_after_refund = client.get("/api/admin/task-board").json()["data"]
|
||
completed_payout = next(
|
||
item
|
||
for item in board_after_refund
|
||
if item.get("record_kind") == "ask_batch"
|
||
and item["id"] == review["assignment_id"]
|
||
)
|
||
assert completed_payout["status_label"] == "已完成"
|
||
assert completed_payout["completed_count"] == 1
|
||
audit_history = client.get("/api/admin/ask-completion-reviews").json()["data"]
|
||
assert not any(item["assignment_id"] == review["assignment_id"] for item in audit_history)
|
||
refreshed = client.get(url)
|
||
assert refreshed.json()["data"]["status"] == "已通过"
|
||
|
||
|
||
def test_approved_answer_appears_as_pending_refund_with_detail():
|
||
previous_key = settings.llm_api_key
|
||
settings.llm_api_key = ""
|
||
phone = f"19{uuid4().int % 1_000_000_000:09d}"
|
||
try:
|
||
with TestClient(app) as client:
|
||
login = client.post("/api/answer/auth/phone-login", json={"phone": phone})
|
||
token = login.json()["data"]["token"]
|
||
created = client.post(
|
||
"/api/answer/me/submissions",
|
||
data={"token": token},
|
||
files=[("files", ("invited.png", b"invited-image", "image/png"))],
|
||
).json()["data"]
|
||
question_id = created["questions"][0]["id"]
|
||
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
assert client.patch(f"/api/admin/answer-task-drafts/questions/{question_id}", json={"selected": True}).status_code == 200
|
||
assert client.post(f"/api/admin/answer-task-drafts/{created['id']}/publish").status_code == 200
|
||
completed = client.post(
|
||
f"/api/answer/me/submissions/{created['id']}/completion",
|
||
data={"token": token},
|
||
files=[
|
||
("files", ("done.png", b"done-image", "image/png")),
|
||
("payment_files", ("payment.png", b"payment-image", "image/png")),
|
||
],
|
||
)
|
||
assert completed.status_code == 200
|
||
approved = client.post(
|
||
f"/api/admin/answer-completion-reviews/{created['id']}/decision",
|
||
json={"approved": True},
|
||
)
|
||
assert approved.status_code == 200
|
||
|
||
board = client.get("/api/admin/task-board").json()["data"]
|
||
payout = next(
|
||
item
|
||
for item in board
|
||
if item.get("record_kind") == "answer_submission"
|
||
and item["id"] == created["id"]
|
||
)
|
||
assert payout["status_label"] == "待返款"
|
||
detail = client.get(f"/api/admin/task-board/{created['id']}")
|
||
assert detail.status_code == 200
|
||
assert len(detail.json()["data"]["completion_screenshots"]) == 1
|
||
assert len(detail.json()["data"]["payment_screenshots"]) == 1
|
||
refunded = client.post(payout["refund_endpoint"])
|
||
assert refunded.status_code == 200
|
||
assert refunded.json()["data"]["status_label"] == "已完成"
|
||
assert refunded.json()["data"]["refund_endpoint"] is None
|
||
board_after_refund = client.get("/api/admin/task-board").json()["data"]
|
||
completed_payout = next(
|
||
item
|
||
for item in board_after_refund
|
||
if item.get("record_kind") == "answer_submission"
|
||
and item["id"] == created["id"]
|
||
)
|
||
assert completed_payout["status_label"] == "已完成"
|
||
review_history = client.get("/api/admin/answer-completion-reviews").json()["data"]
|
||
assert not any(item["id"] == created["id"] for item in review_history)
|
||
finally:
|
||
settings.llm_api_key = previous_key
|
||
|
||
|
||
def test_public_ask_questions_are_unique_across_users_and_eventually_exhausted():
|
||
with TestClient(app) as client:
|
||
assert client.post("/api/admin/auth/login", json={"username": "operator", "password": "operator123456"}).status_code == 200
|
||
generated = client.post(
|
||
"/api/admin/ask-tasks/generate",
|
||
json={
|
||
"shop_name": "唯一分配测试店铺",
|
||
"product_link": "https://item.example.com/unique-allocation",
|
||
"product_name": "跨用户唯一分配测试商品",
|
||
"question_count": 10,
|
||
},
|
||
).json()["data"]
|
||
assert client.post(f"/api/admin/ask-tasks/{generated['id']}/publish").status_code == 200
|
||
|
||
sessions = []
|
||
for _ in range(3):
|
||
phone = f"138{uuid4().int % 100_000_000:08d}"
|
||
sessions.append(client.post("/api/ask/auth/phone-login", json={"phone": phone}).json()["data"])
|
||
|
||
first_url = f"/api/ask/tasks/{generated['task_id']}?token={sessions[0]['token']}"
|
||
second_url = f"/api/ask/tasks/{generated['task_id']}?token={sessions[1]['token']}"
|
||
third_url = f"/api/ask/tasks/{generated['task_id']}?token={sessions[2]['token']}"
|
||
first = client.get(first_url)
|
||
second = client.get(second_url)
|
||
|
||
assert first.status_code == 200
|
||
assert second.status_code == 200
|
||
first_ids = {item["id"] for item in first.json()["data"]["questions"]}
|
||
second_ids = {item["id"] for item in second.json()["data"]["questions"]}
|
||
assert len(first_ids) == 5
|
||
assert len(second_ids) == 5
|
||
assert first_ids.isdisjoint(second_ids)
|
||
assert len(first_ids | second_ids) == 10
|
||
|
||
# Returning users keep their original allocation.
|
||
assert {item["id"] for item in client.get(first_url).json()["data"]["questions"]} == first_ids
|
||
|
||
exhausted = client.get(third_url)
|
||
assert exhausted.status_code == 409
|
||
assert exhausted.json()["detail"] == "本次任务已被领取完,请关注下一个任务!"
|