mirror of
https://github.com/zhtyyx/ioe.git
synced 2026-06-03 21:02:59 +08:00
270 lines
8.9 KiB
Python
270 lines
8.9 KiB
Python
import os
|
|
import tempfile
|
|
|
|
from django.test import TestCase, Client
|
|
from django.urls import reverse
|
|
from django.contrib.auth.models import User, Permission, Group
|
|
from decimal import Decimal
|
|
|
|
from inventory.models import (
|
|
Category,
|
|
Product,
|
|
Inventory,
|
|
InventoryTransaction,
|
|
Member,
|
|
MemberLevel,
|
|
Sale,
|
|
SaleItem
|
|
)
|
|
|
|
class ViewTestCase(TestCase):
|
|
"""视图测试的基类"""
|
|
|
|
def setUp(self):
|
|
# 创建测试用户
|
|
self.user = User.objects.create_user(
|
|
username='testuser',
|
|
password='12345',
|
|
email='test@example.com'
|
|
)
|
|
|
|
# 创建测试管理员
|
|
self.admin = User.objects.create_user(
|
|
username='admin',
|
|
password='admin123',
|
|
email='admin@example.com',
|
|
is_staff=True
|
|
)
|
|
|
|
# 创建客户端
|
|
self.client = Client()
|
|
|
|
# 创建测试分类
|
|
self.category = Category.objects.create(
|
|
name='测试分类',
|
|
description='测试分类描述'
|
|
)
|
|
|
|
# 创建测试商品
|
|
self.product = Product.objects.create(
|
|
barcode='1234567890',
|
|
name='测试商品',
|
|
category=self.category,
|
|
description='测试商品描述',
|
|
price=Decimal('10.00'),
|
|
cost=Decimal('5.00')
|
|
)
|
|
|
|
# 创建库存记录
|
|
self.inventory = Inventory.objects.create(
|
|
product=self.product,
|
|
quantity=100,
|
|
warning_level=10
|
|
)
|
|
|
|
# 创建会员等级
|
|
self.member_level = MemberLevel.objects.create(
|
|
name='普通会员',
|
|
discount=Decimal('0.95'), # 95%
|
|
points_threshold=0,
|
|
color='#FF5733'
|
|
)
|
|
|
|
# 创建会员
|
|
self.member = Member.objects.create(
|
|
name='测试会员',
|
|
phone='13800138000',
|
|
level=self.member_level,
|
|
balance=Decimal('100.00'),
|
|
points=0
|
|
)
|
|
|
|
class ProductViewTest(ViewTestCase):
|
|
"""测试商品相关视图"""
|
|
|
|
def test_product_list_view(self):
|
|
"""测试商品列表视图"""
|
|
# 登录
|
|
self.client.login(username='testuser', password='12345')
|
|
|
|
# 访问商品列表页面
|
|
response = self.client.get(reverse('product_list'))
|
|
|
|
# 验证响应
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, 'inventory/product_list.html')
|
|
self.assertContains(response, '测试商品')
|
|
|
|
def test_product_create_view(self):
|
|
"""测试创建商品视图"""
|
|
# 登录
|
|
self.client.login(username='testuser', password='12345')
|
|
|
|
# 访问创建商品页面
|
|
response = self.client.get(reverse('product_create'))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, 'inventory/product_form.html')
|
|
|
|
# 提交创建商品表单
|
|
product_data = {
|
|
'barcode': '9876543210',
|
|
'name': '新测试商品',
|
|
'category': self.category.id,
|
|
'description': '新测试商品描述',
|
|
'price': '15.00',
|
|
'cost': '7.50'
|
|
}
|
|
|
|
response = self.client.post(reverse('product_create'), product_data)
|
|
|
|
# 验证重定向
|
|
self.assertRedirects(response, reverse('product_list'))
|
|
|
|
# 验证商品创建
|
|
self.assertTrue(Product.objects.filter(barcode='9876543210').exists())
|
|
|
|
class InventoryViewTest(ViewTestCase):
|
|
"""测试库存相关视图"""
|
|
|
|
def test_inventory_list_view(self):
|
|
"""测试库存列表视图"""
|
|
# 登录
|
|
self.client.login(username='testuser', password='12345')
|
|
|
|
# 访问库存列表页面
|
|
response = self.client.get(reverse('inventory_list'))
|
|
|
|
# 验证响应
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, 'inventory/inventory_list.html')
|
|
self.assertContains(response, '测试商品')
|
|
|
|
def test_inventory_transaction_create_view(self):
|
|
"""测试创建库存交易视图"""
|
|
# 登录
|
|
self.client.login(username='testuser', password='12345')
|
|
|
|
# 访问创建库存交易页面
|
|
response = self.client.get(reverse('inventory_create'))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, 'inventory/inventory_form.html')
|
|
|
|
# 提交创建库存交易表单
|
|
transaction_data = {
|
|
'product': self.product.id,
|
|
'quantity': '50',
|
|
'notes': '测试入库'
|
|
}
|
|
|
|
response = self.client.post(reverse('inventory_create'), transaction_data)
|
|
|
|
# 验证重定向
|
|
self.assertRedirects(response, reverse('inventory_list'))
|
|
|
|
# 验证库存交易创建和库存更新
|
|
self.assertTrue(InventoryTransaction.objects.filter(product=self.product, quantity=50).exists())
|
|
|
|
# 刷新库存对象
|
|
self.inventory.refresh_from_db()
|
|
self.assertEqual(self.inventory.quantity, 150) # 100 + 50
|
|
|
|
|
|
class MemberApiViewTest(ViewTestCase):
|
|
"""测试会员相关 API 视图"""
|
|
|
|
def test_member_search_requires_login(self):
|
|
"""未登录用户不能通过手机号查询会员隐私信息"""
|
|
url = reverse('member_search_by_phone', args=[self.member.phone])
|
|
|
|
response = self.client.get(url)
|
|
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertIn('/accounts/login/', response['Location'])
|
|
self.assertIn('next=', response['Location'])
|
|
|
|
def test_member_search_returns_data_for_authenticated_user(self):
|
|
"""登录用户仍可使用会员搜索 API"""
|
|
self.client.login(username='testuser', password='12345')
|
|
url = reverse('member_search_by_phone', args=[self.member.phone])
|
|
|
|
response = self.client.get(url)
|
|
|
|
self.assertEqual(response.status_code, 200)
|
|
data = response.json()
|
|
self.assertTrue(data['success'])
|
|
self.assertEqual(data['member_id'], self.member.id)
|
|
self.assertEqual(data['member_phone'], self.member.phone)
|
|
|
|
|
|
class SaleViewTest(ViewTestCase):
|
|
"""测试销售相关视图"""
|
|
|
|
def test_sale_list_view(self):
|
|
"""测试销售列表视图"""
|
|
# 登录
|
|
self.client.login(username='testuser', password='12345')
|
|
|
|
# 访问销售列表页面
|
|
response = self.client.get(reverse('sale_list'))
|
|
|
|
# 验证响应
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, 'inventory/sale_list.html')
|
|
|
|
def test_sale_create_view(self):
|
|
"""测试创建销售视图"""
|
|
# 登录
|
|
self.client.login(username='testuser', password='12345')
|
|
|
|
# 访问创建销售页面
|
|
response = self.client.get(reverse('sale_create'))
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, 'inventory/sale_form.html')
|
|
|
|
# 提交创建销售表单
|
|
sale_data = {
|
|
'payment_method': 'cash',
|
|
'member': self.member.id
|
|
}
|
|
|
|
response = self.client.post(reverse('sale_create'), sale_data)
|
|
|
|
# 验证创建销售单
|
|
self.assertTrue(Sale.objects.filter(member=self.member).exists())
|
|
sale = Sale.objects.filter(member=self.member).first()
|
|
|
|
# 验证重定向到销售项创建页面
|
|
self.assertRedirects(response, reverse('sale_item_create', args=[sale.id]))
|
|
|
|
|
|
class BackupViewSecurityTest(TestCase):
|
|
"""备份管理视图的安全回归测试"""
|
|
|
|
def setUp(self):
|
|
self.client = Client()
|
|
self.user = User.objects.create_superuser(
|
|
username='backup-admin',
|
|
password='backup-pass',
|
|
email='backup@example.com'
|
|
)
|
|
self.client.force_login(self.user)
|
|
|
|
self.temp_parent = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp_parent.cleanup)
|
|
self.backup_root = os.path.join(self.temp_parent.name, 'backups')
|
|
self.temp_dir = os.path.join(self.temp_parent.name, 'temp')
|
|
os.makedirs(self.backup_root, exist_ok=True)
|
|
os.makedirs(self.temp_dir, exist_ok=True)
|
|
|
|
def test_delete_backup_rejects_parent_directory_traversal(self):
|
|
sentinel_path = os.path.join(self.temp_parent.name, 'keep.txt')
|
|
with open(sentinel_path, 'w', encoding='utf-8') as sentinel:
|
|
sentinel.write('do not delete')
|
|
|
|
with self.settings(BACKUP_ROOT=self.backup_root, TEMP_DIR=self.temp_dir):
|
|
response = self.client.post('/system/backup/delete/../', {'confirm': 'on'})
|
|
|
|
self.assertRedirects(response, reverse('backup_list'))
|
|
self.assertTrue(os.path.exists(sentinel_path))
|
|
self.assertTrue(os.path.isdir(self.backup_root))
|