"""웹서버 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"