test_auto_post_api.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import os
  2. import json
  3. import pytest
  4. from datetime import datetime, timedelta
  5. from fastapi.testclient import TestClient
  6. from unittest.mock import patch, MagicMock
  7. # 导入被测试的API
  8. import sys
  9. current_dir = os.path.dirname(os.path.abspath(__file__))
  10. backend_dir = os.path.dirname(os.path.dirname(current_dir))
  11. if backend_dir not in sys.path:
  12. sys.path.insert(0, backend_dir)
  13. from api.auto_post_api import router
  14. from fastapi import FastAPI
  15. # 创建测试应用
  16. app = FastAPI()
  17. app.include_router(router)
  18. client = TestClient(app)
  19. # 测试数据
  20. TEST_IMAGE_DIR = os.path.join(os.path.dirname(__file__), "test_data")
  21. os.makedirs(TEST_IMAGE_DIR, exist_ok=True)
  22. def create_test_image(filename, content=b"fake image content"):
  23. """创建测试图片文件"""
  24. filepath = os.path.join(TEST_IMAGE_DIR, filename)
  25. with open(filepath, "wb") as f:
  26. f.write(content)
  27. return filepath
  28. @pytest.fixture(autouse=True)
  29. def setup_teardown():
  30. """测试前创建必要的目录和文件,测试后清理"""
  31. # 设置
  32. os.makedirs("temp", exist_ok=True)
  33. test_image1 = create_test_image("flower.jpg")
  34. test_image2 = create_test_image("test_img.png")
  35. yield
  36. # 清理
  37. import shutil
  38. if os.path.exists("temp"):
  39. shutil.rmtree("temp")
  40. if os.path.exists(TEST_IMAGE_DIR):
  41. shutil.rmtree(TEST_IMAGE_DIR)
  42. def get_test_data(future_time=None):
  43. """获取测试数据"""
  44. if future_time is None:
  45. future_time = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d %H:%M")
  46. return {
  47. "title": "💙被问爆的蓝色仙女裙,美到犯规!",
  48. "description": "👗宝子们,挖到一条超绝的蓝色长裙!温柔的浅蓝色,仿佛把天空穿在了身上~V领设计巧妙修饰颈部线条,增添了一丝小性感。泡泡袖又带着点复古甜美感,谁穿谁是在逃公主!",
  49. "topics": json.dumps(["每日穿搭", "今日分享", "蓝色长裙", "仙女裙", "复古甜美", "小性感", "氛围感", "闭眼入不亏"]),
  50. "schedule_time": future_time
  51. }
  52. @patch("api.auto_post_api.AutoPostService")
  53. def test_successful_post(mock_service):
  54. """测试成功发帖场景"""
  55. # 模拟服务层返回
  56. mock_instance = mock_service.return_value
  57. mock_instance.post_to_xiaohongshu.return_value = {"status": "success"}
  58. # 准备测试数据
  59. with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1, \
  60. open(os.path.join(TEST_IMAGE_DIR, "test_img.png"), "rb") as f2:
  61. files = [
  62. ("files", ("flower.jpg", f1, "image/jpeg")),
  63. ("files", ("test_img.png", f2, "image/png"))
  64. ]
  65. response = client.post(
  66. "/xiaohongshu/post",
  67. files=files,
  68. data=get_test_data()
  69. )
  70. # 验证响应
  71. assert response.status_code == 200
  72. assert response.json()["status"] == "success"
  73. # 验证服务层调用
  74. mock_instance.post_to_xiaohongshu.assert_called_once()
  75. def test_invalid_schedule_time():
  76. """测试无效的定时发布时间"""
  77. past_time = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d %H:%M")
  78. data = get_test_data(past_time)
  79. with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1:
  80. files = [("files", ("flower.jpg", f1, "image/jpeg"))]
  81. response = client.post(
  82. "/xiaohongshu/post",
  83. files=files,
  84. data=data
  85. )
  86. assert response.status_code == 422
  87. assert "定时发布时间不能早于当前时间" in response.text
  88. def test_invalid_topics_format():
  89. """测试无效的话题格式"""
  90. data = get_test_data()
  91. data["topics"] = "invalid json"
  92. with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1:
  93. files = [("files", ("flower.jpg", f1, "image/jpeg"))]
  94. response = client.post(
  95. "/xiaohongshu/post",
  96. files=files,
  97. data=data
  98. )
  99. assert response.status_code == 400
  100. assert "话题必须是数组格式" in response.text
  101. def test_too_many_topics():
  102. """测试超过话题数量限制"""
  103. data = get_test_data()
  104. # 创建21个话题(超过20个的限制)
  105. data["topics"] = json.dumps([f"话题{i}" for i in range(21)])
  106. with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1:
  107. files = [("files", ("flower.jpg", f1, "image/jpeg"))]
  108. response = client.post(
  109. "/xiaohongshu/post",
  110. files=files,
  111. data=data
  112. )
  113. assert response.status_code == 422
  114. assert "topics" in response.text
  115. def test_too_many_images():
  116. """测试超过最大图片数量限制"""
  117. # 创建10张测试图片(超过9张的限制)
  118. files = []
  119. for i in range(10):
  120. create_test_image(f"test{i}.jpg")
  121. with open(os.path.join(TEST_IMAGE_DIR, f"test{i}.jpg"), "rb") as f:
  122. files.append(("files", (f"test{i}.jpg", f, "image/jpeg")))
  123. response = client.post(
  124. "/xiaohongshu/post",
  125. files=files,
  126. data=get_test_data()
  127. )
  128. assert response.status_code == 422
  129. assert "files" in response.text
  130. def test_missing_required_fields():
  131. """测试缺少必填字段"""
  132. data = get_test_data()
  133. del data["title"] # 删除必填字段
  134. with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1:
  135. files = [("files", ("flower.jpg", f1, "image/jpeg"))]
  136. response = client.post(
  137. "/xiaohongshu/post",
  138. files=files,
  139. data=data
  140. )
  141. assert response.status_code == 422
  142. assert "title" in response.text
  143. @patch("api.auto_post_api.AutoPostService")
  144. def test_service_error_handling(mock_service):
  145. """测试服务层错误处理"""
  146. # 模拟服务层抛出异常
  147. mock_instance = mock_service.return_value
  148. mock_instance.post_to_xiaohongshu.side_effect = Exception("发布失败")
  149. with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1:
  150. files = [("files", ("flower.jpg", f1, "image/jpeg"))]
  151. response = client.post(
  152. "/xiaohongshu/post",
  153. files=files,
  154. data=get_test_data()
  155. )
  156. assert response.status_code == 500
  157. assert "Failed to post to Xiaohongshu" in response.text
  158. def test_empty_image():
  159. """测试空图片文件"""
  160. create_test_image("empty.jpg", content=b"")
  161. data = get_test_data()
  162. with open(os.path.join(TEST_IMAGE_DIR, "empty.jpg"), "rb") as f:
  163. files = [("files", ("empty.jpg", f, "image/jpeg"))]
  164. response = client.post(
  165. "/xiaohongshu/post",
  166. files=files,
  167. data=data
  168. )
  169. assert response.status_code == 400
  170. assert "empty file" in response.text.lower()
  171. def test_invalid_image_format():
  172. """测试无效的图片格式"""
  173. create_test_image("invalid.txt", content=b"this is not an image")
  174. data = get_test_data()
  175. with open(os.path.join(TEST_IMAGE_DIR, "invalid.txt"), "rb") as f:
  176. files = [("files", ("invalid.txt", f, "text/plain"))]
  177. response = client.post(
  178. "/xiaohongshu/post",
  179. files=files,
  180. data=data
  181. )
  182. assert response.status_code == 400
  183. assert "image format" in response.text.lower()