Files
jsahn 4e943700fc feat: add common utilities and feedback handling scripts
- 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.
2026-10-06 23:14:45 +09:00

127 lines
6.1 KiB
Python

"""웹서버 API 회귀 테스트 (임시 DB에 data/atlas.json을 가져와 실행)."""
def first_term(client):
d = client.get("/api/atlas").json()
return d, d["terms"][0]["i"]
def test_atlas_shape(client):
d, _ = first_term(client)
assert len(d["galaxies"]) == 9
assert len(d["terms"]) > 1000
assert d["sections"] and d["relations"]
t = d["terms"][0]
assert {"i", "n", "g", "s", "st", "a"} <= t.keys()
def test_term_detail_and_404(client):
_, tid = first_term(client)
t = client.get(f"/api/terms/{tid}").json()
assert t["term_id"] == tid and "sources" in t and "relations" in t
assert client.get("/api/terms/t_nope").status_code == 404
def test_feedback_flow(client, admin):
_, tid = first_term(client)
r = client.post("/api/feedback", json={"kind": "content", "category": "뜻·정의가 틀림", "target_type": "term",
"target_id": tid, "message": "정의가 잘렸어요", "contact": "a@b.c"})
fid = r.json()["id"]
pub = client.get("/api/feedback").json()["items"][0]
assert pub["id"] == fid and "contact" not in pub
r = admin.patch(f"/api/admin/feedback/{fid}", json={"status": "resolved", "resolution": "수정함"})
assert r.json()["status"] == "resolved"
assert admin.get(f"/api/admin/feedback/{fid}").json()["handled_by"] == "교수자"
assert client.post("/api/feedback", json={"kind": "content", "message": "x"}).status_code == 422
assert client.post("/api/feedback", json={"kind": "content", "message": "없는 용어", "target_type": "term",
"target_id": "t_nope"}).status_code == 400
def test_client_error_dedup(client, admin):
for _ in range(3):
client.post("/api/client-errors", json={"message": "TypeError: boom", "stack": "at a.js:1"})
items = admin.get("/api/admin/feedback?kind=client_error").json()["items"]
assert len(items) == 1 and items[0]["occurrences"] == 3
def test_admin_requires_login(client):
assert client.get("/api/admin/stats").status_code == 401
assert client.post("/api/admin/login", json={"username": "professor", "password": "wrong"}).status_code == 401
assert client.post("/api/admin/login", json={"username": "professor", "password": "test-pass"}).status_code == 401 # 공용 비밀번호 없음
def test_term_edit_survives_reseed(admin):
_, tid = first_term(admin)
r = admin.put(f"/api/admin/terms/{tid}", json={"easy": "검수한 설명", "review_status": "confirmed"})
assert r.status_code == 200 and r.json()["easy"] == "검수한 설명"
n_src = len(r.json()["sources"])
admin.post("/api/admin/reseed")
t = admin.get(f"/api/admin/terms/{tid}").json()
assert t["easy"] == "검수한 설명" and t["review_status"] == "confirmed"
assert len(t["sources"]) == n_src # 출처 중복 없음
def test_public_status_filter(owner):
admin = owner
_, tid = first_term(admin)
admin.put(f"/api/admin/terms/{tid}", json={"review_status": "confirmed"})
assert admin.put("/api/admin/settings", json={"public_statuses": ["confirmed"]}).status_code == 200
d = admin.get("/api/atlas").json()
assert [t["i"] for t in d["terms"]] == [tid]
def test_relation_crud_and_merge(admin):
d, _ = first_term(admin)
a, b, c = d["terms"][1]["i"], d["terms"][2]["i"], d["terms"][3]["i"]
r = admin.post("/api/admin/relations", json={"source": a, "target": b, "type": "precedes", "label": "다음"})
rid = r.json()["relation_id"]
assert admin.post("/api/admin/relations", json={"source": a, "target": b, "type": "precedes"}).status_code == 409
admin.post("/api/admin/relations", json={"source": c, "target": b, "type": "precedes"})
admin.post(f"/api/admin/terms/{c}/merge", json={"into": a})
rels = admin.get(f"/api/admin/relations?term={a}&limit=1000").json()["items"]
assert sum(1 for x in rels if x["source"] == a and x["target"] == b and x["type"] == "precedes") == 1
assert admin.delete(f"/api/admin/relations/{rid}").json()["ok"]
def test_section_move_moves_terms(admin):
d, _ = first_term(admin)
sec = d["sections"][0]
admin.put(f"/api/admin/toc/{sec['id']}", json={"galaxy_id": "g9"})
terms = admin.get(f"/api/admin/terms?section={sec['id']}&limit=1000").json()["items"]
assert terms and all(t["galaxy_id"] == "g9" for t in terms)
def test_pages_served(client):
for p in ("/", "/feedback", "/admin", "/static/js/atlas.js", "/static/css/base.css"):
assert client.get(p).status_code == 200
def test_no_textbook_info_public(client):
"""저작권: 학생 화면(비로그인)에는 교과서 원문·문장·교재명·쪽수를 보내지 않는다."""
d = client.get("/api/atlas").json()
assert d["books"] == []
assert all("page" not in s and "book_id" not in s for s in d["sections"])
_, tid = first_term(client)
t = client.get(f"/api/terms/{tid}").json()
assert t["sources"] == [] and "definition_text" not in t and "definition_page" not in t
assert client.get("/pdf/x").status_code == 404
def test_admin_keeps_sources(admin):
_, tid = first_term(admin)
t = admin.get(f"/api/admin/terms/{tid}").json()
assert t["sources"] and "book_short" in t["sources"][0]
assert admin.get("/pdf/x").status_code == 404 # 원문 PDF는 관리자에게도 내보내지 않음
def test_unified_login_and_account(client):
# 교수자 아이디로 /api/login → 관리자 세션
r = client.post("/api/login", json={"username": "professor", "password": "owner-pass"}).json()
assert r["kind"] == "admin"
assert client.get("/api/admin/stats").status_code == 200
assert client.put("/api/admin/account", json={"current_password": "wrong", "username": "prof2"}).status_code == 400
r = client.put("/api/admin/account", json={"current_password": "owner-pass", "username": "prof2", "name": "김교수", "new_password": "newpass99"})
assert r.json()["username"] == "prof2"
assert client.post("/api/login", json={"username": "professor", "password": "owner-pass"}).status_code == 401
assert client.post("/api/login", json={"username": "prof2", "password": "newpass99"}).json()["kind"] == "admin"