wendajia/backend/tests/test_auth.py

396 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from fastapi.testclient import TestClient
from uuid import uuid4
from app.main import app
from app.config import settings
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():
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
generated = client.post("/api/admin/ask-tasks/generate", json={"product_link": "https://item.example.com/1", "product_name": "测试商品", "question_count": 3})
assert generated.status_code == 200
task = generated.json()["data"]
published = client.post(f"/api/admin/ask-tasks/{task['id']}/publish")
assert published.status_code == 200
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
def test_ask_task_generation_honors_question_count():
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={
"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_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={
"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
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"] == "已完成"
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={
"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"] == "本次任务已被领取完,请关注下一个任务!"