Files
gsfdc/server/tests/test_edu_column.py

224 lines
7.9 KiB
Python

"""继续教育:在线教育外链卡片与教育公告。工作人员写入,门户公开读取。"""
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": "<p>尚未公示</p>",
"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": "<p>线上与线下安排</p><script>alert(1)</script>",
"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)