"""تست‌های API — مسیرهای عمومی، احراز هویت، CRUD مدیریت، پیش‌نویس مقاله و سئو.""" import re DOC = {"type": "doc", "content": [{"type": "paragraph"}]} # ---------- عمومی ---------- def test_health(client): res = client.get("/api/health") assert res.status_code == 200 assert res.json()["status"] == "ok" def test_site_hides_draft_articles(client): res = client.get("/api/site") assert res.status_code == 200 slugs = [a["slug"] for a in res.json()["articles"]] assert "published-post" in slugs assert "draft-post" not in slugs def test_public_articles_hide_drafts(client): res = client.get("/api/articles") assert all(a["status"] == "published" for a in res.json()) assert client.get("/api/articles/draft-post").status_code == 404 def test_admin_endpoints_reject_anonymous(client): assert client.get("/api/admin/articles").status_code == 401 # ---------- احراز هویت ---------- def test_login_success_and_failure(client): assert ( client.post("/api/auth/login", json={"username": "admin", "password": "wrong"}).status_code == 401 ) res = client.post("/api/auth/login", json={"username": "admin", "password": "test-admin-pass-1"}) assert res.status_code == 200 assert res.json()["access_token"] def test_change_password(auth_client, client): res = auth_client.post( "/api/auth/change-password", json={"current_password": "wrong", "new_password": "brand-new-pass-9"}, ) assert res.status_code == 400 res = auth_client.post( "/api/auth/change-password", json={"current_password": "test-admin-pass-1", "new_password": "brand-new-pass-9"}, ) assert res.status_code == 200 # رمز قدیمی رد و رمز جدید قبول می‌شود assert ( client.post("/api/auth/login", json={"username": "admin", "password": "test-admin-pass-1"}).status_code == 401 ) assert ( client.post("/api/auth/login", json={"username": "admin", "password": "brand-new-pass-9"}).status_code == 200 ) # بازگرداندن رمز برای تست‌های بعدی res = client.post("/api/auth/login", json={"username": "admin", "password": "brand-new-pass-9"}) auth = {"Authorization": f"Bearer {res.json()['access_token']}"} res = client.post( "/api/auth/change-password", json={"current_password": "brand-new-pass-9", "new_password": "test-admin-pass-1"}, headers=auth, ) assert res.status_code == 200 def test_login_rate_limited(client): for _ in range(5): res = client.post("/api/auth/login", json={"username": "admin", "password": "wrong"}) assert res.status_code == 401 res = client.post("/api/auth/login", json={"username": "admin", "password": "wrong"}) assert res.status_code == 429 assert "Retry-After" in res.headers # ---------- CRUD مدیریت ---------- def test_article_crud_and_draft_flow(auth_client, client): created = auth_client.post( "/api/admin/articles", json={"slug": "flow-post", "title": "مقاله جریان", "body": DOC}, ) assert created.status_code == 201, created.text article = created.json() # مقاله تازه پیش‌نویس است و در سایت عمومی دیده نمی‌شود assert article["status"] == "draft" assert "flow-post" not in [a["slug"] for a in client.get("/api/articles").json()] res = auth_client.patch(f"/api/admin/articles/{article['id']}", json={"seo_keyphrase": "ساختمان"}) assert res.json()["seo_keyphrase"] == "ساختمان" res = auth_client.patch(f"/api/admin/articles/{article['id']}", json={"status": "published"}) assert res.status_code == 200 assert "flow-post" in [a["slug"] for a in client.get("/api/articles").json()] assert auth_client.delete(f"/api/admin/articles/{article['id']}").status_code == 204 assert "flow-post" not in [a["slug"] for a in client.get("/api/articles").json()] def test_service_roundtrip_and_reorder(auth_client): ids = [] for name in ("خدمت الف", "خدمت ب"): res = auth_client.post("/api/admin/services", json={"title": name}) assert res.status_code == 201 ids.append(res.json()["id"]) res = auth_client.post("/api/admin/services/reorder", json={"ids": ids[::-1]}) assert res.status_code == 200 ordered = [s["id"] for s in res.json() if s["id"] in ids] assert ordered == ids[::-1] res = auth_client.patch(f"/api/admin/services/{ids[0]}", json={"desc": "توضیح تازه"}) assert res.json()["desc"] == "توضیح تازه" for i in ids: assert auth_client.delete(f"/api/admin/services/{i}").status_code == 204 def test_seo_meta_create_and_patch(auth_client): res = auth_client.post( "/api/admin/seo-meta", json={"page_key": "test-page", "title": "عنوان", "description": "توضیح"} ) assert res.status_code == 201 meta_id = res.json()["id"] res = auth_client.patch(f"/api/admin/seo-meta/{meta_id}", json={"title": "عنوان تازه"}) assert res.json()["title"] == "عنوان تازه" assert auth_client.delete(f"/api/admin/seo-meta/{meta_id}").status_code == 204 # ---------- سئو ---------- def test_robots_txt(client): res = client.get("/robots.txt") assert res.status_code == 200 assert "Disallow: /admin" in res.text assert "Sitemap: https://test.example/sitemap.xml" in res.text def test_sitemap_includes_published_only(client): res = client.get("/sitemap.xml") assert res.status_code == 200 assert "/blog/published-post" in res.text assert "/blog/draft-post" not in res.text assert res.text.startswith("", res.text)) == 1