Files
dreamweaver/backend/tests/test_stories.py
zhangtuo e9d7f8832a Initial commit: clean project structure
- Backend: FastAPI + SQLAlchemy + Celery (Python 3.11+)
- Frontend: Vue 3 + TypeScript + Pinia + Tailwind
- Admin Frontend: separate Vue 3 app for management
- Docker Compose: 9 services orchestration
- Specs: design prototypes, memory system PRD, product roadmap

Cleanup performed:
- Removed temporary debug scripts from backend root
- Removed deprecated admin_app.py (embedded UI)
- Removed duplicate docs from admin-frontend
- Updated .gitignore for Vite cache and egg-info
2026-01-20 18:20:03 +08:00

258 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""故事 API 测试。"""
import time
from unittest.mock import AsyncMock, patch
import pytest
from fastapi.testclient import TestClient
from app.api.stories import _request_log, RATE_LIMIT_REQUESTS
class TestStoryGenerate:
"""故事生成测试。"""
def test_generate_without_auth(self, client: TestClient):
"""未登录时生成故事。"""
response = client.post(
"/api/generate",
json={"type": "keywords", "data": "小兔子, 森林"},
)
assert response.status_code == 401
def test_generate_with_empty_data(self, auth_client: TestClient):
"""空数据生成故事。"""
response = auth_client.post(
"/api/generate",
json={"type": "keywords", "data": ""},
)
assert response.status_code == 422
def test_generate_with_invalid_type(self, auth_client: TestClient):
"""无效类型生成故事。"""
response = auth_client.post(
"/api/generate",
json={"type": "invalid", "data": "test"},
)
assert response.status_code == 422
def test_generate_story_success(self, auth_client: TestClient, mock_text_provider):
"""成功生成故事。"""
response = auth_client.post(
"/api/generate",
json={"type": "keywords", "data": "小兔子, 森林, 勇气"},
)
assert response.status_code == 200
data = response.json()
assert "id" in data
assert "title" in data
assert "story_text" in data
assert data["mode"] == "generated"
class TestStoryList:
"""故事列表测试。"""
def test_list_without_auth(self, client: TestClient):
"""未登录时获取列表。"""
response = client.get("/api/stories")
assert response.status_code == 401
def test_list_empty(self, auth_client: TestClient):
"""空列表。"""
response = auth_client.get("/api/stories")
assert response.status_code == 200
assert response.json() == []
def test_list_with_stories(self, auth_client: TestClient, test_story):
"""有故事时获取列表。"""
response = auth_client.get("/api/stories")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["id"] == test_story.id
assert data[0]["title"] == test_story.title
def test_list_pagination(self, auth_client: TestClient, test_story):
"""分页测试。"""
response = auth_client.get("/api/stories?limit=1&offset=0")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
response = auth_client.get("/api/stories?limit=1&offset=1")
assert response.status_code == 200
data = response.json()
assert len(data) == 0
class TestStoryDetail:
"""故事详情测试。"""
def test_get_story_without_auth(self, client: TestClient, test_story):
"""未登录时获取详情。"""
response = client.get(f"/api/stories/{test_story.id}")
assert response.status_code == 401
def test_get_story_not_found(self, auth_client: TestClient):
"""故事不存在。"""
response = auth_client.get("/api/stories/99999")
assert response.status_code == 404
def test_get_story_success(self, auth_client: TestClient, test_story):
"""成功获取详情。"""
response = auth_client.get(f"/api/stories/{test_story.id}")
assert response.status_code == 200
data = response.json()
assert data["id"] == test_story.id
assert data["title"] == test_story.title
assert data["story_text"] == test_story.story_text
class TestStoryDelete:
"""故事删除测试。"""
def test_delete_without_auth(self, client: TestClient, test_story):
"""未登录时删除。"""
response = client.delete(f"/api/stories/{test_story.id}")
assert response.status_code == 401
def test_delete_not_found(self, auth_client: TestClient):
"""删除不存在的故事。"""
response = auth_client.delete("/api/stories/99999")
assert response.status_code == 404
def test_delete_success(self, auth_client: TestClient, test_story):
"""成功删除故事。"""
response = auth_client.delete(f"/api/stories/{test_story.id}")
assert response.status_code == 200
assert response.json()["message"] == "Deleted"
response = auth_client.get(f"/api/stories/{test_story.id}")
assert response.status_code == 404
class TestRateLimit:
"""Rate limit 测试。"""
def setup_method(self):
"""每个测试前清理 rate limit 缓存。"""
_request_log.clear()
def test_rate_limit_allows_normal_requests(self, auth_client: TestClient, test_story):
"""正常请求不触发限流。"""
for _ in range(RATE_LIMIT_REQUESTS - 1):
response = auth_client.get(f"/api/stories/{test_story.id}")
assert response.status_code == 200
def test_rate_limit_blocks_excess_requests(self, auth_client: TestClient, test_story):
"""超限请求被阻止。"""
for _ in range(RATE_LIMIT_REQUESTS):
auth_client.get(f"/api/stories/{test_story.id}")
response = auth_client.get(f"/api/stories/{test_story.id}")
assert response.status_code == 429
assert "Too many requests" in response.json()["detail"]
class TestImageGenerate:
"""封面图片生成测试。"""
def test_generate_image_without_auth(self, client: TestClient, test_story):
"""未登录时生成图片。"""
response = client.post(f"/api/image/generate/{test_story.id}")
assert response.status_code == 401
def test_generate_image_not_found(self, auth_client: TestClient):
"""故事不存在。"""
response = auth_client.post("/api/image/generate/99999")
assert response.status_code == 404
class TestAudio:
"""语音朗读测试。"""
def test_get_audio_without_auth(self, client: TestClient, test_story):
"""未登录时获取音频。"""
response = client.get(f"/api/audio/{test_story.id}")
assert response.status_code == 401
def test_get_audio_not_found(self, auth_client: TestClient):
"""故事不存在。"""
response = auth_client.get("/api/audio/99999")
assert response.status_code == 404
def test_get_audio_success(self, auth_client: TestClient, test_story, mock_tts_provider):
"""成功获取音频。"""
response = auth_client.get(f"/api/audio/{test_story.id}")
assert response.status_code == 200
assert response.headers["content-type"] == "audio/mpeg"
class TestGenerateFull:
"""完整故事生成测试(/api/generate/full"""
def test_generate_full_without_auth(self, client: TestClient):
"""未登录时生成完整故事。"""
response = client.post(
"/api/generate/full",
json={"type": "keywords", "data": "小兔子, 森林"},
)
assert response.status_code == 401
def test_generate_full_success(self, auth_client: TestClient, mock_text_provider, mock_image_provider):
"""成功生成完整故事(含图片)。"""
response = auth_client.post(
"/api/generate/full",
json={"type": "keywords", "data": "小兔子, 森林, 勇气"},
)
assert response.status_code == 200
data = response.json()
assert "id" in data
assert "title" in data
assert "story_text" in data
assert data["mode"] == "generated"
assert data["image_url"] == "https://example.com/image.png"
assert data["audio_ready"] is False # 音频按需生成
assert data["errors"] == {}
def test_generate_full_image_failure(self, auth_client: TestClient, mock_text_provider):
"""图片生成失败时返回部分成功。"""
with patch("app.api.stories.generate_image", new_callable=AsyncMock) as mock_img:
mock_img.side_effect = Exception("Image API error")
response = auth_client.post(
"/api/generate/full",
json={"type": "keywords", "data": "小兔子, 森林"},
)
assert response.status_code == 200
data = response.json()
assert data["image_url"] is None
assert "image" in data["errors"]
assert "Image API error" in data["errors"]["image"]
def test_generate_full_with_education_theme(self, auth_client: TestClient, mock_text_provider, mock_image_provider):
"""带教育主题生成故事。"""
response = auth_client.post(
"/api/generate/full",
json={
"type": "keywords",
"data": "小兔子, 森林",
"education_theme": "勇气与友谊",
},
)
assert response.status_code == 200
mock_text_provider.assert_called_once()
call_kwargs = mock_text_provider.call_args.kwargs
assert call_kwargs["education_theme"] == "勇气与友谊"
class TestImageGenerateSuccess:
"""封面图片生成成功测试。"""
def test_generate_image_success(self, auth_client: TestClient, test_story, mock_image_provider):
"""成功生成图片。"""
response = auth_client.post(f"/api/image/generate/{test_story.id}")
assert response.status_code == 200
data = response.json()
assert data["image_url"] == "https://example.com/image.png"