Files
TermiNexus 31b0ab885f refactor(orm): 手写 ORM 下沉 gvsdsdk Manager,通用工具改为 SDK 转发
P1 通用查询:viewsets / app / menu / mail / gitea / mc / stats / sync_apps 等视图与命令的裸查询改委托 SDK Manager(Role/Company/Department/Position/Menu/Tenant/App)。

P2 App 体系:app_manager.py 与 app_manifests.py 实现零裸 ORM,职务多值/虚拟职务/公司下拉全部走 SDK。

P3 工具下沉:money/json_encoder/celery_utils/exception_handler/redis_lock 改为纯转发展开;ip_filter/storage 转发并注入 app.ini 配置源;common 下 DateTime/String/Naming/UUID 转发 SDK。

删除死代码 commission/strategies/engine.py(SDK 已有同物且零引用)与 payment/models.py 的 FromTenantAndPlatform;单测 patch 目标同步为 gvsdsdk.*,test_attack_detect fixture 补齐限频去重状态。
2026-09-13 21:58:06 +08:00

165 lines
5.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模块:money.py 单元测试
覆盖 ToDecimal / QuantizeYuan / YuanToFen / FenToYuan /
FormatYuan / AddYuan / MultiplyYuan / SafeDecimalOperation,
重点验证边界值、浮点规避、银行家舍入、非法输入降级。
"""
from decimal import Decimal, ROUND_HALF_UP
from django.test import SimpleTestCase
from gvsdsdk.money import (
ToDecimal, QuantizeYuan, YuanToFen, FenToYuan,
FormatYuan, AddYuan, MultiplyYuan, SafeDecimalOperation,
)
class ToDecimalTestCase(SimpleTestCase):
"""ToDecimal 安全转换。"""
def testToDecimal_None_ReturnsDefault(self):
self.assertEqual(ToDecimal(None), Decimal('0'))
def testToDecimal_EmptyString_ReturnsDefault(self):
self.assertEqual(ToDecimal(''), Decimal('0'))
def testToDecimal_Decimal_Passthrough(self):
d = Decimal('25.05')
self.assertIs(ToDecimal(d), d)
def testToDecimal_Float_ConvertsViaStr(self):
# 0.1 float 直接转 Decimal 会得到 0.1000000000000000055...
# 通过 str 转换避免精度污染
self.assertEqual(ToDecimal(0.1), Decimal('0.1'))
def testToDecimal_ValidString(self):
self.assertEqual(ToDecimal('25.005'), Decimal('25.005'))
def testToDecimal_Int(self):
self.assertEqual(ToDecimal(100), Decimal('100'))
def testToDecimal_InvalidString_ReturnsDefault(self):
self.assertEqual(ToDecimal('abc'), Decimal('0'))
def testToDecimal_InvalidString_CustomDefault(self):
self.assertEqual(ToDecimal('abc', default=Decimal('-1')), Decimal('-1'))
class QuantizeYuanTestCase(SimpleTestCase):
"""QuantizeYuan 银行家舍入。"""
def testQuantizeYuan_BankersRounding(self):
# 25.005 → 25.00(偶数舍入,0 是偶数)
self.assertEqual(QuantizeYuan('25.005'), Decimal('25.00'))
# 25.015 → 25.02(偶数舍入,2 是偶数)
self.assertEqual(QuantizeYuan('25.015'), Decimal('25.02'))
def testQuantizeYuan_HalfUpOverride(self):
self.assertEqual(
QuantizeYuan('25.005', rounding=ROUND_HALF_UP),
Decimal('25.01'),
)
def testQuantizeYuan_TruncatesExtraDigits(self):
self.assertEqual(QuantizeYuan('25.1'), Decimal('25.10'))
self.assertEqual(QuantizeYuan('25.123456'), Decimal('25.12'))
class YuanFenConversionTestCase(SimpleTestCase):
"""元/分互转。"""
def testYuanToFen_NormalValue(self):
self.assertEqual(YuanToFen('25.01'), 2501)
def testYuanToFen_RoundsBeforeConvert(self):
# 25.005 银行家舍入到 25.00 → 2500
self.assertEqual(YuanToFen('25.005'), 2500)
def testYuanToFen_Zero(self):
self.assertEqual(YuanToFen('0'), 0)
def testYuanToFen_Negative(self):
self.assertEqual(YuanToFen('-1.23'), -123)
def testFenToYuan_NormalValue(self):
self.assertEqual(FenToYuan(2501), Decimal('25.01'))
def testFenToYuan_Zero(self):
self.assertEqual(FenToYuan(0), Decimal('0.00'))
def testFenToYuan_Negative(self):
self.assertEqual(FenToYuan(-123), Decimal('-1.23'))
def testYuanFen_RoundTrip(self):
for yuan in ['0.01', '1.00', '99.99', '1000000.00']:
self.assertEqual(FenToYuan(YuanToFen(yuan)), Decimal(yuan))
class FormatYuanTestCase(SimpleTestCase):
"""FormatYuan 格式化。"""
def testFormatYuan_PlainString(self):
self.assertEqual(FormatYuan('25.1'), '25.10')
def testFormatYuan_WithComma(self):
self.assertEqual(FormatYuan('1234.5', with_comma=True), '1,234.50')
def testFormatYuan_LargeWithComma(self):
self.assertEqual(FormatYuan('1000000', with_comma=True), '1,000,000.00')
class AddYuanTestCase(SimpleTestCase):
"""AddYuan 多值求和。"""
def testAddYuan_EmptyArgs_ReturnsZero(self):
self.assertEqual(AddYuan(), Decimal('0.00'))
def testAddYuan_MultipleValues(self):
self.assertEqual(AddYuan('1.01', '2.02', '3.03'), Decimal('6.06'))
def testAddYuan_MixedTypes(self):
self.assertEqual(AddYuan(1, '0.50', Decimal('0.25')), Decimal('1.75'))
def testAddYuan_SkipsInvalid(self):
# None → default 0
self.assertEqual(AddYuan('1.00', None, 'abc'), Decimal('1.00'))
class MultiplyYuanTestCase(SimpleTestCase):
"""MultiplyYuan 单价×数量。"""
def testMultiplyYuan_IntegerQuantity(self):
self.assertEqual(MultiplyYuan('9.99', 3), Decimal('29.97'))
def testMultiplyYuan_DecimalQuantity(self):
self.assertEqual(MultiplyYuan('10.00', Decimal('2.5')), Decimal('25.00'))
def testMultiplyYuan_StringQuantity(self):
self.assertEqual(MultiplyYuan('100.00', '0.1'), Decimal('10.00'))
class SafeDecimalOperationTestCase(SimpleTestCase):
"""SafeDecimalOperation 装饰器。"""
def testSafeDecimalOperation_NormalReturn(self):
@SafeDecimalOperation(default=Decimal('-1'))
def Calc(a, b):
return ToDecimal(a) / ToDecimal(b)
self.assertEqual(Calc('10', '2'), Decimal('5'))
def testSafeDecimalOperation_ExceptionReturnsDefault(self):
@SafeDecimalOperation(default=Decimal('-1'))
def Calc(a, b):
return ToDecimal(a) / ToDecimal(b)
# 除零触发 ArithmeticError → 返回 default
self.assertEqual(Calc('10', '0'), Decimal('-1'))
def testSafeDecimalOperation_TypeErrorReturnsDefault(self):
@SafeDecimalOperation(default=Decimal('0'))
def Calc(value):
# Decimal + str 触发 TypeError
return Decimal('1') + value
self.assertEqual(Calc('not-a-number'), Decimal('0'))