"""继续教育:在线教育外链卡片与教育公告。工作人员写入,门户公开读取。""" import pytest from fastapi.testclient import TestClient from app.core.config import get_settings from app.core.security import create_access_token @pytest.fixture def client(monkeypatch): monkeypatch.setenv("JWT_SECRET", "test-secret-must-be-32-characters!") monkeypatch.setenv("API_PAYLOAD_ENCRYPT", "false") get_settings.cache_clear() if not get_settings().database_url.strip(): pytest.skip("DATABASE_URL 为空") from app.main import create_app with TestClient(create_app()) as api: yield api get_settings.cache_clear() def _headers(token_type: str) -> dict[str, str]: return {"Authorization": f"Bearer {create_access_token('1', token_type)}"} def _minio_ready() -> bool: try: from app.services.storage import ensure_buckets ensure_buckets() return True except Exception: return False def _png() -> bytes: return ( b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01" b"\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc\xf8\x0f" b"\x00\x00\x01\x01\x00\x05\x18\xd8N\x00\x00\x00\x00IEND\xaeB`\x82" ) def _clear_notices(client, staff: dict[str, str]) -> None: listed = client.get("/api/admin/about/articles/edu_notices", headers=staff) assert listed.status_code == 200, listed.text for item in listed.json().get("items") or []: deleted = client.delete(f"/api/admin/home/articles/{item['id']}", headers=staff) assert deleted.status_code in (200, 204), deleted.text def test_edu_platforms_are_public_cards_and_notices_follow_publish_rules(client): if not _minio_ready(): pytest.skip("MinIO 不可达") staff = _headers("staff") file_ids: list[int] = [] try: empty = client.get("/api/site/edu/platforms") assert empty.status_code == 200, empty.text assert empty.json()["placeholder"] == "暂无在线教育" missing = client.put( "/api/admin/edu/platforms", headers=staff, json={"items": [{"name": "省继续教育", "url": "https://example.com/edu", "sortOrder": 1}]}, ) assert missing.status_code == 400 assert "图片" in missing.text script_url = client.put( "/api/admin/edu/platforms", headers=staff, json={ "items": [ { "name": "省继续教育", "url": "javascript:alert(1)", "imageFileId": 1, "sortOrder": 1, } ] }, ) assert script_url.status_code == 400 cover = client.post( "/api/admin/files", headers=staff, data={"bucket": "public"}, files={"file": ("platform.png", _png(), "image/png")}, ) assert cover.status_code == 200, cover.text file_ids.append(cover.json()["id"]) second = client.post( "/api/admin/files", headers=staff, data={"bucket": "public"}, files={"file": ("later.png", _png(), "image/png")}, ) assert second.status_code == 200, second.text file_ids.append(second.json()["id"]) saved = client.put( "/api/admin/edu/platforms", headers=staff, json={ "items": [ { "name": "后配置的平台", "url": "https://example.com/later", "imageFileId": file_ids[1], "sortOrder": 2, }, { "name": "甘肃省继续教育", "url": "https://example.com/edu", "imageFileId": file_ids[0], "sortOrder": 1, }, ] }, ) assert saved.status_code == 200, saved.text admin_list = client.get("/api/admin/edu/platforms", headers=staff) assert admin_list.status_code == 200, admin_list.text assert [item["name"] for item in admin_list.json()["items"]] == [ "甘肃省继续教育", "后配置的平台", ] public = client.get("/api/site/edu/platforms") assert public.status_code == 200, public.text body = public.json() assert "placeholder" not in body assert [item["name"] for item in body["items"]] == ["甘肃省继续教育", "后配置的平台"] first = body["items"][0] assert first["url"] == "https://example.com/edu" assert first["navigation"] == "newWindow" assert first["imageUrl"] assert "imageFileId" not in first rejected = client.put( "/api/admin/edu/platforms", headers=_headers("member"), json={"items": []}, ) assert rejected.status_code in (401, 403) assert client.get("/api/site/edu/platforms").json()["items"][0]["name"] == "甘肃省继续教育" shortcut = client.put( "/api/admin/home/menu", headers=staff, json={ "items": [ { "key": "edu", "label": "继续教育", "url": "https://example.com/edu", "visible": True, "sortOrder": 1, } ] }, ) assert shortcut.status_code == 400 labels = [item["label"] for item in client.get("/api/site/home").json()["shortcutMenu"]["items"]] assert "继续教育" not in labels _clear_notices(client, staff) empty_notices = client.get("/api/site/about/articles/edu_notices") assert empty_notices.status_code == 200, empty_notices.text assert empty_notices.json()["placeholder"] == "暂无教育公告" draft = client.post( "/api/admin/home/articles", headers=staff, json={ "category": "edu_notices", "title": "内部培训草稿", "published": False, "bodyHtml": "

尚未公示

", "attachmentFileIds": [], }, ) assert draft.status_code == 200, draft.text published = client.post( "/api/admin/home/articles", headers=staff, json={ "category": "edu_notices", "title": "2026年度培训计划", "published": True, "bodyHtml": "

线上与线下安排

", "imageFileId": file_ids[0], "attachmentFileIds": [], }, ) assert published.status_code == 200, published.text notices = client.get("/api/site/about/articles/edu_notices") assert notices.status_code == 200, notices.text titles = [item["title"] for item in notices.json()["items"]] assert titles == ["2026年度培训计划"] assert "内部培训草稿" not in notices.text assert notices.json()["items"][0]["imageUrl"] detail = client.get(f"/api/site/home/articles/{published.json()['id']}") assert detail.status_code == 200, detail.text assert "线上与线下安排" in detail.text assert "script" not in detail.text assert "alert" not in detail.text home = client.get("/api/admin/home", headers=staff) categories = {item["category"] for item in home.json()["articles"]} assert "edu_notices" not in categories finally: client.put("/api/admin/edu/platforms", headers=staff, json={"items": []}) _clear_notices(client, staff) for file_id in file_ids: client.delete(f"/api/admin/files/{file_id}", headers=staff)