import json from pathlib import Path from django.conf import settings from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase, override_settings from django.urls import reverse from menu.models import Dish, DishCategory, Order TEST_MEDIA_ROOT = Path(settings.BASE_DIR) / 'test_media' @override_settings(MEDIA_ROOT=TEST_MEDIA_ROOT) class UploadImageTests(TestCase): def tearDown(self): if TEST_MEDIA_ROOT.exists(): for path in sorted(TEST_MEDIA_ROOT.rglob('*'), reverse=True): if path.is_file(): path.unlink() elif path.is_dir(): path.rmdir() def test_upload_rejects_invalid_extension(self): image = SimpleUploadedFile('bad.txt', b'file-content', content_type='image/png') response = self.client.post(reverse('upload'), {'image': image}) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()['error'], 'Unsupported image extension') def test_upload_rejects_invalid_content_type(self): image = SimpleUploadedFile('dish.png', b'file-content', content_type='text/plain') response = self.client.post(reverse('upload'), {'image': image}) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()['error'], 'Unsupported image content type') @override_settings(MAX_UPLOAD_IMAGE_SIZE=4) def test_upload_rejects_oversized_file(self): image = SimpleUploadedFile('dish.png', b'12345', content_type='image/png') response = self.client.post(reverse('upload'), {'image': image}) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()['error'], 'Image file too large') def test_upload_returns_absolute_media_url(self): image = SimpleUploadedFile('dish.png', b'png-bytes', content_type='image/png') response = self.client.post(reverse('upload'), {'image': image}, secure=True) self.assertEqual(response.status_code, 200) data = response.json() self.assertTrue(data['url'].startswith('https://testserver/media/dishes/')) class ParseUrlTests(TestCase): def test_parse_url_rejects_non_allowlisted_host(self): response = self.client.post( '/api/parse-url/', data=json.dumps({'url': 'http://127.0.0.1/internal'}), content_type='application/json', ) self.assertEqual(response.status_code, 400) self.assertEqual(response.json()['error'], '该站点不在允许的解析范围内') class OrderApiTests(TestCase): def setUp(self): category = DishCategory.objects.create(name='热菜') self.dish1 = Dish.objects.create(name='宫保鸡丁', category=category, ingredients=[{'name': '鸡肉', 'amount': '300g'}]) self.dish2 = Dish.objects.create(name='鱼香肉丝', category=category, ingredients=[{'name': '猪肉', 'amount': '200g'}]) def test_create_order_binds_dish_ids_and_returns_saved_object(self): payload = { 'name': '周末聚会', 'dish_ids': [self.dish1.id, self.dish2.id], 'participants': ['张三', '李四'], 'party_date': '2026-03-10T12:30:00Z', } response = self.client.post('/api/orders/', payload, content_type='application/json') self.assertEqual(response.status_code, 201) data = response.json() self.assertEqual(data['name'], '周末聚会') self.assertEqual(sorted(d['id'] for d in data['dishes_detail']), sorted([self.dish1.id, self.dish2.id])) self.assertEqual(data['participants'], ['张三', '李四']) order = Order.objects.get(pk=data['id']) self.assertEqual(order.dishes.count(), 2) self.assertEqual(sorted(order.dishes.values_list('id', flat=True)), sorted([self.dish1.id, self.dish2.id]))