import os import json import pytest from datetime import datetime, timedelta from fastapi.testclient import TestClient from unittest.mock import patch, MagicMock # 导入被测试的API import sys current_dir = os.path.dirname(os.path.abspath(__file__)) backend_dir = os.path.dirname(os.path.dirname(current_dir)) if backend_dir not in sys.path: sys.path.insert(0, backend_dir) from api.auto_post_api import router from fastapi import FastAPI # 创建测试应用 app = FastAPI() app.include_router(router) client = TestClient(app) # 测试数据 TEST_IMAGE_DIR = os.path.join(os.path.dirname(__file__), "test_data") os.makedirs(TEST_IMAGE_DIR, exist_ok=True) def create_test_image(filename, content=b"fake image content"): """创建测试图片文件""" filepath = os.path.join(TEST_IMAGE_DIR, filename) with open(filepath, "wb") as f: f.write(content) return filepath @pytest.fixture(autouse=True) def setup_teardown(): """测试前创建必要的目录和文件,测试后清理""" # 设置 os.makedirs("temp", exist_ok=True) test_image1 = create_test_image("flower.jpg") test_image2 = create_test_image("test_img.png") yield # 清理 import shutil if os.path.exists("temp"): shutil.rmtree("temp") if os.path.exists(TEST_IMAGE_DIR): shutil.rmtree(TEST_IMAGE_DIR) def get_test_data(future_time=None): """获取测试数据""" if future_time is None: future_time = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d %H:%M") return { "title": "💙被问爆的蓝色仙女裙,美到犯规!", "description": "👗宝子们,挖到一条超绝的蓝色长裙!温柔的浅蓝色,仿佛把天空穿在了身上~V领设计巧妙修饰颈部线条,增添了一丝小性感。泡泡袖又带着点复古甜美感,谁穿谁是在逃公主!", "topics": json.dumps(["每日穿搭", "今日分享", "蓝色长裙", "仙女裙", "复古甜美", "小性感", "氛围感", "闭眼入不亏"]), "schedule_time": future_time } @patch("api.auto_post_api.AutoPostService") def test_successful_post(mock_service): """测试成功发帖场景""" # 模拟服务层返回 mock_instance = mock_service.return_value mock_instance.post_to_xiaohongshu.return_value = {"status": "success"} # 准备测试数据 with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1, \ open(os.path.join(TEST_IMAGE_DIR, "test_img.png"), "rb") as f2: files = [ ("files", ("flower.jpg", f1, "image/jpeg")), ("files", ("test_img.png", f2, "image/png")) ] response = client.post( "/xiaohongshu/post", files=files, data=get_test_data() ) # 验证响应 assert response.status_code == 200 assert response.json()["status"] == "success" # 验证服务层调用 mock_instance.post_to_xiaohongshu.assert_called_once() def test_invalid_schedule_time(): """测试无效的定时发布时间""" past_time = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d %H:%M") data = get_test_data(past_time) with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1: files = [("files", ("flower.jpg", f1, "image/jpeg"))] response = client.post( "/xiaohongshu/post", files=files, data=data ) assert response.status_code == 422 assert "定时发布时间不能早于当前时间" in response.text def test_invalid_topics_format(): """测试无效的话题格式""" data = get_test_data() data["topics"] = "invalid json" with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1: files = [("files", ("flower.jpg", f1, "image/jpeg"))] response = client.post( "/xiaohongshu/post", files=files, data=data ) assert response.status_code == 400 assert "话题必须是数组格式" in response.text def test_too_many_topics(): """测试超过话题数量限制""" data = get_test_data() # 创建21个话题(超过20个的限制) data["topics"] = json.dumps([f"话题{i}" for i in range(21)]) with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1: files = [("files", ("flower.jpg", f1, "image/jpeg"))] response = client.post( "/xiaohongshu/post", files=files, data=data ) assert response.status_code == 422 assert "topics" in response.text def test_too_many_images(): """测试超过最大图片数量限制""" # 创建10张测试图片(超过9张的限制) files = [] for i in range(10): create_test_image(f"test{i}.jpg") with open(os.path.join(TEST_IMAGE_DIR, f"test{i}.jpg"), "rb") as f: files.append(("files", (f"test{i}.jpg", f, "image/jpeg"))) response = client.post( "/xiaohongshu/post", files=files, data=get_test_data() ) assert response.status_code == 422 assert "files" in response.text def test_missing_required_fields(): """测试缺少必填字段""" data = get_test_data() del data["title"] # 删除必填字段 with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1: files = [("files", ("flower.jpg", f1, "image/jpeg"))] response = client.post( "/xiaohongshu/post", files=files, data=data ) assert response.status_code == 422 assert "title" in response.text @patch("api.auto_post_api.AutoPostService") def test_service_error_handling(mock_service): """测试服务层错误处理""" # 模拟服务层抛出异常 mock_instance = mock_service.return_value mock_instance.post_to_xiaohongshu.side_effect = Exception("发布失败") with open(os.path.join(TEST_IMAGE_DIR, "flower.jpg"), "rb") as f1: files = [("files", ("flower.jpg", f1, "image/jpeg"))] response = client.post( "/xiaohongshu/post", files=files, data=get_test_data() ) assert response.status_code == 500 assert "Failed to post to Xiaohongshu" in response.text def test_empty_image(): """测试空图片文件""" create_test_image("empty.jpg", content=b"") data = get_test_data() with open(os.path.join(TEST_IMAGE_DIR, "empty.jpg"), "rb") as f: files = [("files", ("empty.jpg", f, "image/jpeg"))] response = client.post( "/xiaohongshu/post", files=files, data=data ) assert response.status_code == 400 assert "empty file" in response.text.lower() def test_invalid_image_format(): """测试无效的图片格式""" create_test_image("invalid.txt", content=b"this is not an image") data = get_test_data() with open(os.path.join(TEST_IMAGE_DIR, "invalid.txt"), "rb") as f: files = [("files", ("invalid.txt", f, "text/plain"))] response = client.post( "/xiaohongshu/post", files=files, data=data ) assert response.status_code == 400 assert "image format" in response.text.lower()