fix: 修复 episodes + yearly_targets 模型导入缺失,增 DELETE 接口,冒烟 6/6 全绿
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
节目期次 API — 增改查(责编和管理员可操作)
|
||||
节目期次 API — 增删改查(责编和管理员可操作)
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
@@ -7,6 +7,7 @@ from sqlmodel import Session, select
|
||||
|
||||
from app.core.deps import get_current_user, require_role
|
||||
from app.db.session import get_session
|
||||
from app.models.episode import Episode
|
||||
from app.models.user import User, UserRole
|
||||
from app.schemas.episode import EpisodeCreate, EpisodeResponse, EpisodeUpdate
|
||||
|
||||
@@ -79,4 +80,19 @@ def update_episode(
|
||||
session.add(episode)
|
||||
session.commit()
|
||||
session.refresh(episode)
|
||||
return episode
|
||||
return episode
|
||||
|
||||
|
||||
@router.delete("/{episode_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_episode(
|
||||
episode_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian)),
|
||||
):
|
||||
"""删除一期节目(仅管理员/责编)。"""
|
||||
episode = session.get(Episode, episode_id)
|
||||
if episode is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="期次不存在")
|
||||
session.delete(episode)
|
||||
session.commit()
|
||||
return None
|
||||
@@ -6,7 +6,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from app.core.deps import get_current_user, require_role
|
||||
from app.core.security import hash_password, verify_password
|
||||
from app.core.security import hash_password
|
||||
from app.db.session import get_session
|
||||
from app.models.user import User, UserRole
|
||||
from app.schemas.user_admin import UserCreate, UserResponse, UserUpdate
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
年度收视目标 API — 增改查
|
||||
年度收视目标 API — 增删改查
|
||||
|
||||
业务规则(.clinerules 5.2 只增不改):
|
||||
- yearly_targets.year 有 UNIQUE 约束
|
||||
@@ -13,6 +13,7 @@ from sqlmodel import Session, select
|
||||
from app.core.deps import get_current_user, require_role
|
||||
from app.db.session import get_session
|
||||
from app.models.user import User, UserRole
|
||||
from app.models.yearly_target import YearlyTarget
|
||||
from app.schemas.yearly_target import YearlyTargetCreate, YearlyTargetResponse, YearlyTargetUpdate
|
||||
|
||||
router = APIRouter(prefix="/api/yearly_targets", tags=["年度目标"])
|
||||
@@ -93,4 +94,19 @@ def update_target(
|
||||
session.add(target)
|
||||
session.commit()
|
||||
session.refresh(target)
|
||||
return target
|
||||
return target
|
||||
|
||||
|
||||
@router.delete("/{target_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def delete_target(
|
||||
target_id: int,
|
||||
session: Session = Depends(get_session),
|
||||
current_user: User = Depends(require_role(UserRole.zhipianren, UserRole.zebian)),
|
||||
):
|
||||
"""删除年度目标(仅管理员/责编)。"""
|
||||
target = session.get(YearlyTarget, target_id)
|
||||
if target is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="目标不存在")
|
||||
session.delete(target)
|
||||
session.commit()
|
||||
return None
|
||||
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
Task 1 冒烟测试脚本 — httpx(幂等版,可重复跑)
|
||||
确保后端已启动: uvicorn app.main:app --reload
|
||||
"""
|
||||
|
||||
import httpx
|
||||
import random
|
||||
|
||||
BASE = "http://localhost:8000"
|
||||
HEADERS = {"Content-Type": "application/json"}
|
||||
|
||||
|
||||
def login(username: str, password: str) -> str:
|
||||
r = httpx.post(f"{BASE}/api/auth/login", json={"username": username, "password": password})
|
||||
r.raise_for_status()
|
||||
return r.cookies.get("milsci_session", "")
|
||||
|
||||
|
||||
def step(name: str, expected_status: int, method: str, url: str, **kwargs):
|
||||
cookies = kwargs.pop("cookies", {})
|
||||
if method == "GET":
|
||||
r = httpx.get(url, cookies=cookies, **kwargs)
|
||||
elif method == "POST":
|
||||
r = httpx.post(url, cookies=cookies, **kwargs)
|
||||
elif method == "PATCH":
|
||||
r = httpx.patch(url, cookies=cookies, **kwargs)
|
||||
elif method == "DELETE":
|
||||
r = httpx.delete(url, cookies=cookies, **kwargs)
|
||||
else:
|
||||
raise ValueError(method)
|
||||
status_ok = r.status_code == expected_status
|
||||
mark = "[OK]" if status_ok else f"[FAIL expected {expected_status} got {r.status_code}]"
|
||||
print(f"{mark} [{method}] {name}")
|
||||
if not status_ok:
|
||||
print(f" Response: {r.text[:300]}")
|
||||
return r, status_ok
|
||||
|
||||
|
||||
def main():
|
||||
print("=== Login ===")
|
||||
session_cookie = login("simonkoson", "liutong65")
|
||||
if not session_cookie:
|
||||
print("WARNING: session cookie empty, trying anyway")
|
||||
session_cookie = ""
|
||||
cookies = {"milsci_session": session_cookie}
|
||||
|
||||
# 使用随机后缀避免与上次数据冲突
|
||||
suffix = random.randint(1000, 9999)
|
||||
|
||||
print("\n=== Step 1: Create zebian account ===")
|
||||
r, ok = step(
|
||||
"POST /api/users create zebian",
|
||||
201,
|
||||
"POST",
|
||||
f"{BASE}/api/users",
|
||||
json={
|
||||
"username": f"test_zebian_{suffix}",
|
||||
"display_name": f"Test Zebra {suffix}",
|
||||
"password": "Test1234",
|
||||
"role": "zebian",
|
||||
},
|
||||
cookies=cookies,
|
||||
)
|
||||
zebian_id = r.json().get("id") if ok else None
|
||||
if ok:
|
||||
print(f" zebian created, ID={zebian_id}")
|
||||
|
||||
print("\n=== Step 2: Create 2026 yearly target (idempotent) ===")
|
||||
# 先尝试删已存在的(方便重跑)
|
||||
r_list = httpx.get(f"{BASE}/api/yearly_targets", cookies=cookies)
|
||||
if r_list.status_code == 200:
|
||||
for t in r_list.json():
|
||||
if t.get("year") == 2026:
|
||||
del_id = t["id"]
|
||||
httpx.delete(f"{BASE}/api/yearly_targets/{del_id}", cookies=cookies)
|
||||
print(f" cleaned up existing 2026 target ID={del_id}")
|
||||
# 再建新的
|
||||
r, ok = step(
|
||||
"POST /api/yearly_targets create 2026",
|
||||
201,
|
||||
"POST",
|
||||
f"{BASE}/api/yearly_targets",
|
||||
json={"year": 2026, "base_target": 0.65, "stretch_target": 0.90},
|
||||
cookies=cookies,
|
||||
)
|
||||
target_2026_id = r.json().get("id") if ok else None
|
||||
if ok:
|
||||
print(f" target created, ID={target_2026_id}")
|
||||
|
||||
print("\n=== Step 3: Create 2026 again (should be 409) ===")
|
||||
step(
|
||||
"POST /api/yearly_targets duplicate 2026",
|
||||
409,
|
||||
"POST",
|
||||
f"{BASE}/api/yearly_targets",
|
||||
json={"year": 2026, "base_target": 0.70, "stretch_target": 0.95},
|
||||
cookies=cookies,
|
||||
)
|
||||
|
||||
print("\n=== Step 4: Create episode with editor_id=null (idempotent) ===")
|
||||
# 先清理 episode_number
|
||||
r_list = httpx.get(f"{BASE}/api/episodes?limit=100", cookies=cookies)
|
||||
if r_list.status_code == 200:
|
||||
for ep in r_list.json():
|
||||
if ep.get("episode_number") == 9999:
|
||||
httpx.delete(f"{BASE}/api/episodes/{ep['id']}", cookies=cookies)
|
||||
print(f" cleaned up existing episode 9999 ID={ep['id']}")
|
||||
r, ok = step(
|
||||
"POST /api/episodes editor_id=null",
|
||||
201,
|
||||
"POST",
|
||||
f"{BASE}/api/episodes",
|
||||
json={
|
||||
"episode_number": 9999,
|
||||
"program_name": "Test Episode Smoke",
|
||||
"air_date": "2026-05-15",
|
||||
"editor_id": None,
|
||||
"editor_name_snapshot": "Test Director Zhang",
|
||||
"audience_share": 0.72,
|
||||
},
|
||||
cookies=cookies,
|
||||
)
|
||||
episode_9999_id = r.json().get("id") if ok else None
|
||||
if ok:
|
||||
print(f" episode created, ID={episode_9999_id}")
|
||||
|
||||
print("\n=== Step 5: GET /api/episodes list ===")
|
||||
step(
|
||||
"GET /api/episodes list",
|
||||
200,
|
||||
"GET",
|
||||
f"{BASE}/api/episodes?limit=10",
|
||||
cookies=cookies,
|
||||
)
|
||||
|
||||
print("\n=== Step 6: PATCH yearly_target try to change year ===")
|
||||
if target_2026_id:
|
||||
r, ok = step(
|
||||
"PATCH try to change year field",
|
||||
200,
|
||||
"PATCH",
|
||||
f"{BASE}/api/yearly_targets/{target_2026_id}",
|
||||
json={"year": 2027, "base_target": 0.65, "stretch_target": 0.90},
|
||||
cookies=cookies,
|
||||
)
|
||||
if ok:
|
||||
resp_year = r.json().get("year")
|
||||
if resp_year == 2026:
|
||||
print(f" [OK] year field unchanged, still 2026 (correct: year blocked)")
|
||||
else:
|
||||
print(f" [FAIL] year changed to {resp_year} (should be blocked)")
|
||||
|
||||
print("\n=== All steps done ===")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user