- Introduced common.js for shared functions including API calls, error reporting, and UI helpers. - Implemented feedback.js for the feedback reporting page, including form handling and displaying feedback status. - Added layout.js for managing galaxy and section layouts in the visualization. - Created login.js for user authentication with session management. - Developed search.js for term searching based on user input and synonyms. - Implemented signup.js for user registration with validation and immediate login.
90 lines
5.2 KiB
Python
90 lines
5.2 KiB
Python
"""회원 가입·승인(소유자만, 반 지정)·사례/문제 회원 전용."""
|
|
from fastapi.testclient import TestClient
|
|
|
|
|
|
def _term_with_example(client):
|
|
d = client.get("/api/atlas").json()
|
|
for t in d["terms"]:
|
|
if t["e"]:
|
|
r = client.get(f"/api/terms/{t['i']}").json()
|
|
if r.get("example_locked"):
|
|
return t["i"]
|
|
raise AssertionError("사례가 있는 용어가 없음")
|
|
|
|
|
|
def test_member_flow(client):
|
|
from server.app import app
|
|
# 비회원: 사례 잠김, 문제 불가
|
|
tid = _term_with_example(client)
|
|
assert client.get(f"/api/members/questions?term={tid}").status_code == 401
|
|
# 가입 코드 없이는 가입 불가
|
|
assert client.post("/api/members/signup", json={"code": "NOPE", "username": "stud1", "password": "secret1", "name": "김학생"}).status_code == 400
|
|
with TestClient(app) as own:
|
|
own.post("/api/admin/login", json={"username": "professor", "password": "owner-pass"})
|
|
assert own.post("/api/admin/signup-codes", json={"max_uses": 2}).status_code == 400 # 반 없는 코드 불가
|
|
cid = own.post("/api/admin/classes", json={"name": "1반"}).json()["class_id"]
|
|
code = own.post("/api/admin/signup-codes", json={"class_id": cid, "max_uses": 2}).json()["code"]
|
|
# 코드로 가입하면 바로 회원 (승인 대기 없음)
|
|
r = client.post("/api/members/signup", json={"code": code.lower(), "username": "stud1", "password": "secret1", "name": "김학생"})
|
|
assert r.json()["status"] == "approved"
|
|
assert client.post("/api/members/signup", json={"code": code, "username": "stud1", "password": "secret1", "name": "x"}).status_code == 409
|
|
client.post("/api/members/login", json={"username": "stud1", "password": "secret1"})
|
|
# 교수자 비밀번호가 아니면 관리 화면에 들어갈 수 없음
|
|
with TestClient(app) as rev:
|
|
assert rev.post("/api/admin/login", json={"username": "professor", "password": "test-pass"}).status_code == 401
|
|
assert rev.get("/api/admin/members").status_code == 401
|
|
|
|
# 승인 후: 사례·문제
|
|
me = client.get("/api/members/me").json()
|
|
assert me["status"] == "approved" and me["class_name"] == "1반" and me["can_view"]
|
|
assert client.get(f"/api/terms/{tid}").json()["example"]
|
|
qs = client.get(f"/api/members/questions?term={tid}").json()["questions"]
|
|
assert qs and all("answer" not in q for q in qs) # 정답은 내려보내지 않음
|
|
q = qs[0]
|
|
res = client.post("/api/members/answer", json={"token": q["token"], "choice": 0}).json()
|
|
assert "correct" in res and "answer" in res
|
|
quiz = client.get("/api/members/quiz?galaxy=g6&n=5").json()["questions"]
|
|
assert len(quiz) == 5
|
|
assert client.get("/api/members/me").json()["attempts"] == 1
|
|
# 위조 토큰 거부
|
|
assert client.post("/api/members/answer", json={"token": q["token"] + "x", "choice": 0}).status_code == 400
|
|
|
|
with TestClient(app) as own:
|
|
own.post("/api/admin/login", json={"username": "professor", "password": "owner-pass"})
|
|
cls = own.get("/api/admin/classes").json()["items"][0]
|
|
assert cls["members"] == 1 and cls["attempts"] == 1
|
|
assert own.delete(f"/api/admin/classes/{cls['class_id']}").status_code == 409
|
|
|
|
|
|
def test_manual_question(admin):
|
|
_, tid = admin.get("/api/atlas").json(), None
|
|
tid = admin.get("/api/atlas").json()["terms"][0]["i"]
|
|
r = admin.post("/api/admin/questions", json={"term_id": tid, "qtype": "ox", "prompt": "맞을까요?", "answer": 0, "review_status": "confirmed"})
|
|
qid = r.json()["question_id"]
|
|
qs = admin.get(f"/api/members/questions?term={tid}").json()["questions"] # 관리자는 볼 수 있음
|
|
mq = next(q for q in qs if q["origin"] == "manual")
|
|
assert mq["choices"] == ["O", "X"]
|
|
assert admin.post("/api/members/answer", json={"token": mq["token"], "choice": 0}).json()["correct"]
|
|
assert admin.post("/api/admin/questions", json={"qtype": "mcq", "prompt": "x?", "choices": ["a"], "answer": 0}).status_code == 400
|
|
assert admin.delete(f"/api/admin/questions/{qid}").json()["ok"]
|
|
|
|
|
|
def test_signup_code_rules(owner):
|
|
c = owner
|
|
cid = c.post("/api/admin/classes", json={"name": "B반"}).json()["class_id"]
|
|
r = c.post("/api/admin/signup-codes", json={"code": "class-b", "class_id": cid, "max_uses": 1}).json()
|
|
assert r["code"] == "CLASS-B"
|
|
from fastapi.testclient import TestClient
|
|
from server.app import app
|
|
with TestClient(app) as s:
|
|
r = s.post("/api/members/signup", json={"code": "class-b", "username": "autostud", "password": "secret1", "name": "자동"})
|
|
assert r.json()["status"] == "approved"
|
|
assert s.post("/api/members/signup", json={"code": "CLASS-B", "username": "late", "password": "secret1", "name": "x"}).status_code == 400 # 횟수 초과
|
|
items = c.get("/api/admin/members?status=approved").json()["items"]
|
|
u = next(x for x in items if x["username"] == "autostud")
|
|
assert u["class_name"] == "B반" and u["signup_code"] == "CLASS-B"
|
|
d = c.get(f"/api/admin/members/{u['user_id']}").json()
|
|
assert "password_hash" not in d and "by_galaxy" in d
|
|
code_id = c.get("/api/admin/signup-codes").json()["items"][0]["code_id"]
|
|
c.put(f"/api/admin/signup-codes/{code_id}", json={"class_id": cid, "active": False})
|