forked from erp-dev/erp
feat: completed
This commit is contained in:
0
cost/tests/__init__.py
Normal file
0
cost/tests/__init__.py
Normal file
145
cost/tests/test_models.py
Normal file
145
cost/tests/test_models.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""Cost 模块模型测试"""
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import IntegrityError
|
||||
from django.test import TestCase
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from cost import models as cost_models
|
||||
|
||||
|
||||
class CostCategoryModelTests(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
|
||||
def test_str_representation(self):
|
||||
cat = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='electricity', name='电费',
|
||||
)
|
||||
self.assertEqual(str(cat), '电费 (electricity)')
|
||||
|
||||
def test_unique_together_merchant_key(self):
|
||||
"""同一商户 same unique_key 不可重复"""
|
||||
cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='electricity', name='电费',
|
||||
)
|
||||
with self.assertRaises(IntegrityError):
|
||||
cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='electricity', name='电费2',
|
||||
)
|
||||
|
||||
def test_different_merchants_same_key_allowed(self):
|
||||
"""不同商户 same unique_key 允许"""
|
||||
other = basic_models.Merchant.objects.create(
|
||||
name='其他商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
cat1 = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='electricity', name='电费',
|
||||
)
|
||||
cat2 = cost_models.CostCategory.objects.create(
|
||||
merchant=other, unique_key='electricity', name='电费',
|
||||
)
|
||||
self.assertNotEqual(cat1.id, cat2.id)
|
||||
|
||||
def test_parent_self_reference(self):
|
||||
"""父类目 self-FK"""
|
||||
parent = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='utilities', name='公共事业',
|
||||
)
|
||||
child = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='electricity', name='电费',
|
||||
parent=parent,
|
||||
)
|
||||
self.assertEqual(child.parent_id, parent.id)
|
||||
self.assertEqual(list(parent.children.all()), [child])
|
||||
|
||||
def test_description_nullable(self):
|
||||
cat = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='key', name='name',
|
||||
)
|
||||
self.assertIsNone(cat.description)
|
||||
|
||||
def test_ordering_by_merchant_then_name(self):
|
||||
other = basic_models.Merchant.objects.create(
|
||||
name='其他商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='z', name='Z类目',
|
||||
)
|
||||
cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='a', name='A类目',
|
||||
)
|
||||
cost_models.CostCategory.objects.create(
|
||||
merchant=other, unique_key='m', name='M类目',
|
||||
)
|
||||
# ordering = ('merchant', 'name') — 同一个 merchant 内按 name 排序
|
||||
own = cost_models.CostCategory.objects.filter(merchant=self.merchant)
|
||||
own_names = [c.name for c in own]
|
||||
self.assertEqual(own_names, ['A类目', 'Z类目']) # name 升序
|
||||
|
||||
|
||||
class CostEntryModelTests(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
self.category = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='transport', name='运输费',
|
||||
)
|
||||
|
||||
def test_str_representation(self):
|
||||
entry = cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
amount=Decimal('150.00'),
|
||||
occurred_at=date(2026, 6, 1),
|
||||
)
|
||||
self.assertIn('运输费', str(entry))
|
||||
self.assertIn('150.00', str(entry))
|
||||
|
||||
def test_default_ordering_by_occurred_at_desc(self):
|
||||
e1 = cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant, category=self.category,
|
||||
amount=Decimal('100.00'), occurred_at=date(2026, 6, 1),
|
||||
)
|
||||
e2 = cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant, category=self.category,
|
||||
amount=Decimal('200.00'), occurred_at=date(2026, 6, 10),
|
||||
)
|
||||
entries = list(cost_models.CostEntry.objects.all())
|
||||
self.assertEqual(entries[0].id, e2.id)
|
||||
self.assertEqual(entries[1].id, e1.id)
|
||||
|
||||
def test_operator_nullable(self):
|
||||
entry = cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
amount=Decimal('100.00'),
|
||||
occurred_at=date(2026, 6, 1),
|
||||
operator=None,
|
||||
)
|
||||
self.assertIsNone(entry.operator_id)
|
||||
|
||||
def test_source_fields_nullable(self):
|
||||
entry = cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
amount=Decimal('100.00'),
|
||||
occurred_at=date(2026, 6, 1),
|
||||
)
|
||||
self.assertIsNone(entry.source_module)
|
||||
self.assertIsNone(entry.source_id)
|
||||
|
||||
def test_image_fields_nullable(self):
|
||||
entry = cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
amount=Decimal('100.00'),
|
||||
occurred_at=date(2026, 6, 1),
|
||||
)
|
||||
self.assertFalse(bool(entry.image1))
|
||||
self.assertFalse(bool(entry.image2))
|
||||
445
cost/tests/test_services.py
Normal file
445
cost/tests/test_services.py
Normal file
@@ -0,0 +1,445 @@
|
||||
"""Cost 模块服务测试"""
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.test import TestCase
|
||||
|
||||
from basic_info import models as basic_models
|
||||
from cost import models as cost_models
|
||||
from cost import services as cost_services
|
||||
from cost.models import CostEntryInput, CostProviderPort
|
||||
|
||||
|
||||
class EnsureCategoryTests(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
self.other_merchant = basic_models.Merchant.objects.create(
|
||||
name='其他商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
|
||||
def test_ensure_category_creates_new(self):
|
||||
"""category_key 不存在时创建新类目"""
|
||||
cat = cost_services.ensure_category(
|
||||
merchant=self.merchant,
|
||||
category_key='electricity',
|
||||
category_name='电费',
|
||||
description='每月电费支出',
|
||||
)
|
||||
self.assertEqual(cat.unique_key, 'electricity')
|
||||
self.assertEqual(cat.name, '电费')
|
||||
self.assertEqual(cat.description, '每月电费支出')
|
||||
self.assertEqual(cat.merchant_id, self.merchant.id)
|
||||
|
||||
def test_ensure_category_returns_existing(self):
|
||||
"""category_key 已存在时返回已有类目"""
|
||||
existing = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='electricity', name='电费',
|
||||
)
|
||||
cat = cost_services.ensure_category(
|
||||
merchant=self.merchant, category_key='electricity', category_name='电力',
|
||||
)
|
||||
self.assertEqual(cat.id, existing.id)
|
||||
self.assertEqual(cat.name, '电费') # 不覆盖已有 name
|
||||
|
||||
def test_ensure_category_empty_key_raises(self):
|
||||
"""空 category_key 抛 ValueError"""
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
cost_services.ensure_category(
|
||||
merchant=self.merchant, category_key='', category_name='电费',
|
||||
)
|
||||
self.assertIn('category_key', str(ctx.exception))
|
||||
|
||||
def test_ensure_category_whitespace_key_raises(self):
|
||||
"""纯空格 category_key 抛 ValueError"""
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
cost_services.ensure_category(
|
||||
merchant=self.merchant, category_key=' ', category_name='电费',
|
||||
)
|
||||
self.assertIn('category_key', str(ctx.exception))
|
||||
|
||||
def test_ensure_category_empty_name_raises(self):
|
||||
"""空 category_name 抛 ValueError"""
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
cost_services.ensure_category(
|
||||
merchant=self.merchant, category_key='key', category_name='',
|
||||
)
|
||||
self.assertIn('category_name', str(ctx.exception))
|
||||
|
||||
def test_ensure_category_different_merchants_same_key(self):
|
||||
"""不同商户相同 key 不冲突"""
|
||||
cat1 = cost_services.ensure_category(
|
||||
merchant=self.merchant, category_key='same_key', category_name='类目A',
|
||||
)
|
||||
cat2 = cost_services.ensure_category(
|
||||
merchant=self.other_merchant, category_key='same_key', category_name='类目B',
|
||||
)
|
||||
self.assertNotEqual(cat1.id, cat2.id)
|
||||
self.assertEqual(cat1.name, '类目A')
|
||||
self.assertEqual(cat2.name, '类目B')
|
||||
|
||||
|
||||
class CreateCostEntryTests(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
self.other_merchant = basic_models.Merchant.objects.create(
|
||||
name='其他商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
self.user = get_user_model().objects.create_user(username='testuser', password='pass12345')
|
||||
self.employee = basic_models.Employee.objects.create(
|
||||
merchant=self.merchant, sys_user=self.user, name='测试员工',
|
||||
)
|
||||
self.other_user = get_user_model().objects.create_user(username='otheruser', password='pass12345')
|
||||
self.other_employee = basic_models.Employee.objects.create(
|
||||
merchant=self.other_merchant, sys_user=self.other_user, name='其他员工',
|
||||
)
|
||||
self.category = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='transport', name='运输费',
|
||||
)
|
||||
self.other_category = cost_models.CostCategory.objects.create(
|
||||
merchant=self.other_merchant, unique_key='transport', name='运输费',
|
||||
)
|
||||
|
||||
def test_create_cost_entry_normal(self):
|
||||
"""正常创建支出记录"""
|
||||
entry = cost_services.create_cost_entry(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
amount=Decimal('150.00'),
|
||||
occurred_at=date(2026, 6, 1),
|
||||
operator=self.employee,
|
||||
source_module='manual',
|
||||
source_id='',
|
||||
remarks='测试备注',
|
||||
)
|
||||
self.assertEqual(entry.amount, Decimal('150.00'))
|
||||
self.assertEqual(entry.occurred_at, date(2026, 6, 1))
|
||||
self.assertEqual(entry.operator_id, self.employee.id)
|
||||
self.assertEqual(entry.merchant_id, self.merchant.id)
|
||||
self.assertEqual(entry.source_module, 'manual')
|
||||
self.assertEqual(entry.remarks, '测试备注')
|
||||
|
||||
def test_create_cost_entry_operator_none(self):
|
||||
"""operator=None 正常创建(provider 场景)"""
|
||||
entry = cost_services.create_cost_entry(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
amount=Decimal('200.00'),
|
||||
occurred_at=date(2026, 6, 2),
|
||||
operator=None,
|
||||
)
|
||||
self.assertIsNone(entry.operator_id)
|
||||
self.assertEqual(entry.amount, Decimal('200.00'))
|
||||
|
||||
def test_create_cost_entry_category_mismatch_merchant_raises(self):
|
||||
"""category 所属商户与 merchant 不一致抛 ValueError"""
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
cost_services.create_cost_entry(
|
||||
merchant=self.merchant,
|
||||
category=self.other_category,
|
||||
amount=Decimal('100.00'),
|
||||
occurred_at=date(2026, 6, 1),
|
||||
operator=self.employee,
|
||||
)
|
||||
self.assertIn('category', str(ctx.exception))
|
||||
|
||||
def test_create_cost_entry_operator_mismatch_merchant_raises(self):
|
||||
"""operator 所属商户与 merchant 不一致抛 ValueError"""
|
||||
with self.assertRaises(ValueError) as ctx:
|
||||
cost_services.create_cost_entry(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
amount=Decimal('100.00'),
|
||||
occurred_at=date(2026, 6, 1),
|
||||
operator=self.other_employee,
|
||||
)
|
||||
self.assertIn('operator', str(ctx.exception))
|
||||
|
||||
def test_create_cost_entry_default_values(self):
|
||||
"""不传 source_module/source_id/remarks 时默认为空字符串"""
|
||||
entry = cost_services.create_cost_entry(
|
||||
merchant=self.merchant,
|
||||
category=self.category,
|
||||
amount=Decimal('50.00'),
|
||||
occurred_at=date(2026, 6, 3),
|
||||
)
|
||||
self.assertEqual(entry.source_module, '')
|
||||
self.assertEqual(entry.source_id, '')
|
||||
self.assertEqual(entry.remarks, '')
|
||||
|
||||
|
||||
class MockCostProvider:
|
||||
"""测试用 Provider"""
|
||||
category_key = 'test_provider'
|
||||
|
||||
def __init__(self, entries):
|
||||
self._entries = entries
|
||||
|
||||
def get_cost_entries(self, *, merchant, start_date, end_date):
|
||||
return self._entries
|
||||
|
||||
|
||||
class CollectFromProviderTests(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
|
||||
def test_collect_creates_entries(self):
|
||||
"""正常采集并创建 CostEntry"""
|
||||
entries = [
|
||||
CostEntryInput(
|
||||
category_key='electricity', category_name='电费',
|
||||
amount=Decimal('300.00'), occurred_at=date(2026, 6, 1),
|
||||
source_module='test', source_id='E001',
|
||||
),
|
||||
CostEntryInput(
|
||||
category_key='water', category_name='水费',
|
||||
amount=Decimal('80.00'), occurred_at=date(2026, 6, 2),
|
||||
source_module='test', source_id='W001',
|
||||
),
|
||||
]
|
||||
provider = MockCostProvider(entries)
|
||||
|
||||
result = cost_services.collect_from_provider(
|
||||
provider=provider,
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 1),
|
||||
end_date=date(2026, 6, 30),
|
||||
)
|
||||
|
||||
self.assertEqual(result['total_seen'], 2)
|
||||
self.assertEqual(result['created_count'], 2)
|
||||
self.assertEqual(result['error_count'], 0)
|
||||
self.assertEqual(len(result['errors']), 0)
|
||||
self.assertEqual(result['provider_category_key'], 'test_provider')
|
||||
|
||||
# 验证数据库
|
||||
self.assertEqual(cost_models.CostEntry.objects.count(), 2)
|
||||
self.assertEqual(cost_models.CostCategory.objects.count(), 2)
|
||||
|
||||
def test_collect_returns_not_list_raises(self):
|
||||
"""Provider 返回非 list 抛 TypeError"""
|
||||
class BadProvider:
|
||||
category_key = 'bad'
|
||||
def get_cost_entries(self, *, merchant, start_date, end_date):
|
||||
return 'not_a_list'
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
cost_services.collect_from_provider(
|
||||
provider=BadProvider(),
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 1),
|
||||
end_date=date(2026, 6, 30),
|
||||
)
|
||||
|
||||
def test_collect_non_entry_input_records_error(self):
|
||||
"""非 CostEntryInput 的条目记入 errors"""
|
||||
provider = MockCostProvider(['not_a_dataclass', 123])
|
||||
|
||||
result = cost_services.collect_from_provider(
|
||||
provider=provider,
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 1),
|
||||
end_date=date(2026, 6, 30),
|
||||
)
|
||||
|
||||
self.assertEqual(result['total_seen'], 2)
|
||||
self.assertEqual(result['created_count'], 0)
|
||||
self.assertEqual(result['error_count'], 2)
|
||||
self.assertEqual(cost_models.CostEntry.objects.count(), 0)
|
||||
|
||||
def test_collect_empty_category_key_records_error(self):
|
||||
"""category_key 为空导致 ValueError 记入 errors"""
|
||||
entries = [
|
||||
CostEntryInput(
|
||||
category_key='', category_name='无key类目',
|
||||
amount=Decimal('100.00'), occurred_at=date(2026, 6, 1),
|
||||
source_module='test', source_id='X001',
|
||||
),
|
||||
]
|
||||
provider = MockCostProvider(entries)
|
||||
|
||||
result = cost_services.collect_from_provider(
|
||||
provider=provider,
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 1),
|
||||
end_date=date(2026, 6, 30),
|
||||
)
|
||||
|
||||
self.assertEqual(result['total_seen'], 1)
|
||||
self.assertEqual(result['created_count'], 0)
|
||||
self.assertEqual(result['error_count'], 1)
|
||||
self.assertIn('category_key', result['errors'][0]['error'])
|
||||
|
||||
def test_collect_empty_list(self):
|
||||
"""空列表返回 created_count=0"""
|
||||
provider = MockCostProvider([])
|
||||
|
||||
result = cost_services.collect_from_provider(
|
||||
provider=provider,
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 1),
|
||||
end_date=date(2026, 6, 30),
|
||||
)
|
||||
|
||||
self.assertEqual(result['total_seen'], 0)
|
||||
self.assertEqual(result['created_count'], 0)
|
||||
self.assertEqual(result['error_count'], 0)
|
||||
|
||||
def test_collect_mixed_valid_invalid_partial_success(self):
|
||||
"""混合合法+非法条目,合法条目正常创建"""
|
||||
entries = [
|
||||
CostEntryInput(
|
||||
category_key='electricity', category_name='电费',
|
||||
amount=Decimal('300.00'), occurred_at=date(2026, 6, 1),
|
||||
source_module='test', source_id='E001',
|
||||
),
|
||||
'invalid_item',
|
||||
CostEntryInput(
|
||||
category_key='', category_name='',
|
||||
amount=Decimal('50.00'), occurred_at=date(2026, 6, 2),
|
||||
source_module='test', source_id='X002',
|
||||
),
|
||||
]
|
||||
provider = MockCostProvider(entries)
|
||||
|
||||
result = cost_services.collect_from_provider(
|
||||
provider=provider,
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 1),
|
||||
end_date=date(2026, 6, 30),
|
||||
)
|
||||
|
||||
self.assertEqual(result['total_seen'], 3)
|
||||
self.assertEqual(result['created_count'], 1)
|
||||
self.assertEqual(result['error_count'], 2)
|
||||
self.assertEqual(cost_models.CostEntry.objects.count(), 1)
|
||||
|
||||
def test_collect_reuses_existing_category(self):
|
||||
"""已有类目时不重复创建"""
|
||||
cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='electricity', name='电费旧名',
|
||||
)
|
||||
entries = [
|
||||
CostEntryInput(
|
||||
category_key='electricity', category_name='电费新名',
|
||||
amount=Decimal('100.00'), occurred_at=date(2026, 6, 1),
|
||||
source_module='test', source_id='E001',
|
||||
),
|
||||
]
|
||||
provider = MockCostProvider(entries)
|
||||
|
||||
cost_services.collect_from_provider(
|
||||
provider=provider,
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 1),
|
||||
end_date=date(2026, 6, 30),
|
||||
)
|
||||
|
||||
self.assertEqual(cost_models.CostCategory.objects.count(), 1)
|
||||
cat = cost_models.CostCategory.objects.first()
|
||||
self.assertEqual(cat.name, '电费旧名') # 不覆盖已有 name
|
||||
|
||||
|
||||
class AggregateByCategoryTests(TestCase):
|
||||
def setUp(self):
|
||||
self.merchant = basic_models.Merchant.objects.create(
|
||||
name='测试商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
self.other_merchant = basic_models.Merchant.objects.create(
|
||||
name='其他商户', type=basic_models.MerchantTypeEnum.STORE,
|
||||
)
|
||||
self.cat_a = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='electricity', name='电费',
|
||||
)
|
||||
self.cat_b = cost_models.CostCategory.objects.create(
|
||||
merchant=self.merchant, unique_key='water', name='水费',
|
||||
)
|
||||
|
||||
def _create_entries(self):
|
||||
"""创建测试数据:电费 300+200=500,水费 80,其他商户 1000"""
|
||||
cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant, category=self.cat_a,
|
||||
amount=Decimal('300.00'), occurred_at=date(2026, 6, 1),
|
||||
)
|
||||
cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant, category=self.cat_a,
|
||||
amount=Decimal('200.00'), occurred_at=date(2026, 6, 10),
|
||||
)
|
||||
cost_models.CostEntry.objects.create(
|
||||
merchant=self.merchant, category=self.cat_b,
|
||||
amount=Decimal('80.00'), occurred_at=date(2026, 6, 5),
|
||||
)
|
||||
other_cat = cost_models.CostCategory.objects.create(
|
||||
merchant=self.other_merchant, unique_key='other', name='其他',
|
||||
)
|
||||
cost_models.CostEntry.objects.create(
|
||||
merchant=self.other_merchant, category=other_cat,
|
||||
amount=Decimal('1000.00'), occurred_at=date(2026, 6, 15),
|
||||
)
|
||||
|
||||
def test_aggregate_by_category_desc_order(self):
|
||||
"""按 total_amount 降序,只统计本商户"""
|
||||
self._create_entries()
|
||||
result = cost_services.aggregate_by_category(merchant=self.merchant)
|
||||
|
||||
self.assertEqual(len(result), 2)
|
||||
self.assertEqual(result[0]['category_name'], '电费')
|
||||
self.assertEqual(result[0]['total_amount'], '500.00')
|
||||
self.assertEqual(result[0]['entry_count'], 2)
|
||||
self.assertEqual(result[1]['category_name'], '水费')
|
||||
self.assertEqual(result[1]['total_amount'], '80.00')
|
||||
self.assertEqual(result[1]['entry_count'], 1)
|
||||
|
||||
def test_aggregate_by_category_with_start_date(self):
|
||||
"""start_date 过滤"""
|
||||
self._create_entries()
|
||||
result = cost_services.aggregate_by_category(
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 5),
|
||||
)
|
||||
self.assertEqual(len(result), 2)
|
||||
totals = {r['category_name']: r['total_amount'] for r in result}
|
||||
self.assertEqual(totals['电费'], '200.00') # 只有 6.10 那条
|
||||
self.assertEqual(totals['水费'], '80.00')
|
||||
|
||||
def test_aggregate_by_category_with_end_date(self):
|
||||
"""end_date 过滤"""
|
||||
self._create_entries()
|
||||
result = cost_services.aggregate_by_category(
|
||||
merchant=self.merchant,
|
||||
end_date=date(2026, 6, 5),
|
||||
)
|
||||
self.assertEqual(len(result), 2)
|
||||
totals = {r['category_name']: r['total_amount'] for r in result}
|
||||
self.assertEqual(totals['电费'], '300.00') # 只有 6.1 那条
|
||||
self.assertEqual(totals['水费'], '80.00')
|
||||
|
||||
def test_aggregate_by_category_with_both_dates(self):
|
||||
"""start_date + end_date 同时过滤"""
|
||||
self._create_entries()
|
||||
result = cost_services.aggregate_by_category(
|
||||
merchant=self.merchant,
|
||||
start_date=date(2026, 6, 5),
|
||||
end_date=date(2026, 6, 8),
|
||||
)
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(result[0]['category_name'], '水费')
|
||||
self.assertEqual(result[0]['total_amount'], '80.00')
|
||||
|
||||
def test_aggregate_no_data_returns_empty(self):
|
||||
"""无数据返回空列表"""
|
||||
result = cost_services.aggregate_by_category(merchant=self.merchant)
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_aggregate_includes_category_key(self):
|
||||
"""返回结果包含 category_key"""
|
||||
self._create_entries()
|
||||
result = cost_services.aggregate_by_category(merchant=self.merchant)
|
||||
self.assertEqual(result[0]['category_key'], 'electricity')
|
||||
self.assertEqual(result[1]['category_key'], 'water')
|
||||
Reference in New Issue
Block a user