feat: 益选 OCR 订单处理系统初始提交
- 智能供应商识别(蓉城易购/烟草/杨碧月/通用) - 百度 OCR 表格识别集成 - 规则引擎(列映射/数据清洗/单位转换/规格推断) - 条码映射管理与云端同步(Gitea REST API) - 云端同步支持:条码映射、供应商配置、商品资料、采购模板 - 拖拽一键处理(图片→OCR→Excel→合并) - 191 个单元测试 - 移除无用的模板管理功能 - 清理 IDE 产物目录 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
"""app.core.excel.validators 单元测试"""
|
||||
|
||||
import pytest
|
||||
from app.core.excel.validators import ProductValidator
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def validator():
|
||||
return ProductValidator()
|
||||
|
||||
|
||||
class TestValidateBarcode:
|
||||
"""条码验证测试"""
|
||||
|
||||
def test_valid_barcode_13_digits(self, validator):
|
||||
ok, val, err = validator.validate_barcode("6920584471055")
|
||||
assert ok is True
|
||||
assert val == "6920584471055"
|
||||
assert err is None
|
||||
|
||||
def test_valid_barcode_8_digits(self, validator):
|
||||
ok, val, err = validator.validate_barcode("12345678")
|
||||
assert ok is True
|
||||
assert val == "12345678"
|
||||
|
||||
def test_valid_barcode_12_digits(self, validator):
|
||||
ok, val, err = validator.validate_barcode("692058447105")
|
||||
assert ok is True
|
||||
|
||||
def test_none_returns_invalid(self, validator):
|
||||
ok, val, err = validator.validate_barcode(None)
|
||||
assert ok is False
|
||||
assert err == "条码为空"
|
||||
|
||||
def test_warehouse_identifier(self, validator):
|
||||
ok, val, err = validator.validate_barcode("仓库")
|
||||
assert ok is False
|
||||
assert val == "仓库"
|
||||
assert err == "条码为仓库标识"
|
||||
|
||||
def test_warehouse_full_name(self, validator):
|
||||
ok, val, err = validator.validate_barcode("仓库全名")
|
||||
assert ok is False
|
||||
|
||||
def test_prefix_5_to_6_correction(self, validator):
|
||||
"""5开头(非53)的长条码应修正为6开头"""
|
||||
ok, val, err = validator.validate_barcode("5920584471055")
|
||||
assert ok is True
|
||||
assert val.startswith("6")
|
||||
assert val == "6920584471055"
|
||||
|
||||
def test_prefix_53_not_corrected(self, validator):
|
||||
"""53开头的条码不修正"""
|
||||
ok, val, err = validator.validate_barcode("5321545613000")
|
||||
assert ok is True
|
||||
assert val.startswith("53")
|
||||
|
||||
def test_14_digit_trailing_zero_truncated(self, validator):
|
||||
"""14位条码末尾是0时截断到13位"""
|
||||
ok, val, err = validator.validate_barcode("69205844710550")
|
||||
assert ok is True
|
||||
assert len(val) == 13
|
||||
|
||||
def test_14_digit_no_trailing_zero_invalid(self, validator):
|
||||
"""14位条码末尾不是0时报错"""
|
||||
ok, val, err = validator.validate_barcode("69205844710551")
|
||||
assert ok is False
|
||||
assert "长度异常" in err
|
||||
|
||||
def test_too_short_invalid(self, validator):
|
||||
ok, val, err = validator.validate_barcode("1234567")
|
||||
assert ok is False
|
||||
assert "长度异常" in err
|
||||
|
||||
def test_too_long_invalid(self, validator):
|
||||
ok, val, err = validator.validate_barcode("1" * 14)
|
||||
# 14 digits with trailing 0s gets truncated, but "111...1" has no trailing 0
|
||||
ok2, val2, err2 = validator.validate_barcode("1" * 15)
|
||||
assert ok2 is False
|
||||
|
||||
def test_no_digits_invalid(self, validator):
|
||||
ok, val, err = validator.validate_barcode("abc")
|
||||
assert ok is False
|
||||
assert err == "条码不包含数字"
|
||||
|
||||
def test_float_input_cleaned(self, validator):
|
||||
"""浮点数输入应清理为整数字符串"""
|
||||
ok, val, err = validator.validate_barcode(6920584471055.0)
|
||||
assert ok is True
|
||||
assert val == "6920584471055"
|
||||
|
||||
def test_special_barcode_5321545613(self, validator):
|
||||
"""特殊条码 5321545613 应通过验证"""
|
||||
ok, val, err = validator.validate_barcode("5321545613")
|
||||
assert ok is True
|
||||
assert val == "5321545613"
|
||||
|
||||
|
||||
class TestValidatePrice:
|
||||
"""单价验证测试"""
|
||||
|
||||
def test_valid_price(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price(10.5)
|
||||
assert ok is True
|
||||
assert val == 10.5
|
||||
assert is_gift is False
|
||||
|
||||
def test_zero_price_is_gift(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price(0)
|
||||
assert ok is True
|
||||
assert val == 0.0
|
||||
assert is_gift is True
|
||||
|
||||
def test_none_is_gift(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price(None)
|
||||
assert ok is False
|
||||
assert is_gift is True
|
||||
|
||||
def test_gift_string(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price("赠品")
|
||||
assert ok is True
|
||||
assert is_gift is True
|
||||
|
||||
def test_gift_english(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price("gift")
|
||||
assert ok is True
|
||||
assert is_gift is True
|
||||
|
||||
def test_price_string_with_yen(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price("¥123.45")
|
||||
assert ok is True
|
||||
assert val == 123.45
|
||||
assert is_gift is False
|
||||
|
||||
def test_price_string_with_comma(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price("1,234.56")
|
||||
assert ok is True
|
||||
assert val == 1234.56
|
||||
|
||||
def test_negative_price_invalid(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price(-5)
|
||||
assert ok is False
|
||||
assert is_gift is True
|
||||
|
||||
def test_empty_string_is_gift(self, validator):
|
||||
ok, val, is_gift, err = validator.validate_price("")
|
||||
assert ok is True
|
||||
assert is_gift is True
|
||||
|
||||
|
||||
class TestValidateQuantity:
|
||||
"""数量验证测试"""
|
||||
|
||||
def test_valid_quantity(self, validator):
|
||||
ok, val, err = validator.validate_quantity(10)
|
||||
assert ok is True
|
||||
assert val == 10.0
|
||||
|
||||
def test_float_quantity(self, validator):
|
||||
ok, val, err = validator.validate_quantity(2.5)
|
||||
assert ok is True
|
||||
assert val == 2.5
|
||||
|
||||
def test_string_quantity(self, validator):
|
||||
ok, val, err = validator.validate_quantity("15")
|
||||
assert ok is True
|
||||
assert val == 15.0
|
||||
|
||||
def test_string_with_unit(self, validator):
|
||||
ok, val, err = validator.validate_quantity("10瓶")
|
||||
assert ok is True
|
||||
assert val == 10.0
|
||||
|
||||
def test_none_invalid(self, validator):
|
||||
ok, val, err = validator.validate_quantity(None)
|
||||
assert ok is False
|
||||
assert err == "数量为空"
|
||||
|
||||
def test_zero_invalid(self, validator):
|
||||
ok, val, err = validator.validate_quantity(0)
|
||||
assert ok is False
|
||||
assert "必须大于0" in err
|
||||
|
||||
def test_negative_invalid(self, validator):
|
||||
ok, val, err = validator.validate_quantity(-3)
|
||||
assert ok is False
|
||||
assert "必须大于0" in err
|
||||
|
||||
def test_non_numeric_string_invalid(self, validator):
|
||||
ok, val, err = validator.validate_quantity("abc")
|
||||
assert ok is False
|
||||
assert err == "数量不包含数字"
|
||||
|
||||
|
||||
class TestValidateProduct:
|
||||
"""商品数据整体验证测试"""
|
||||
|
||||
def test_valid_product(self, validator):
|
||||
product = {
|
||||
'barcode': '6920584471055',
|
||||
'price': 10.5,
|
||||
'quantity': 5,
|
||||
'amount': 52.5,
|
||||
}
|
||||
result = validator.validate_product(product)
|
||||
assert result['barcode'] == '6920584471055'
|
||||
assert result['price'] == 10.5
|
||||
assert result['quantity'] == 5.0
|
||||
assert result.get('is_gift') is None or result.get('is_gift') is False
|
||||
|
||||
def test_gift_product(self, validator):
|
||||
product = {
|
||||
'barcode': '6920584471055',
|
||||
'price': '赠品',
|
||||
'quantity': 5,
|
||||
}
|
||||
result = validator.validate_product(product)
|
||||
assert result['is_gift'] is True
|
||||
assert result['price'] == 0.0
|
||||
|
||||
def test_quantity_from_amount_and_price(self, validator):
|
||||
"""数量为空时,通过金额/单价计算"""
|
||||
product = {
|
||||
'barcode': '6920584471055',
|
||||
'price': 10.0,
|
||||
'amount': 50.0,
|
||||
'quantity': None,
|
||||
}
|
||||
result = validator.validate_product(product)
|
||||
assert result['quantity'] == 5.0 # 50 / 10
|
||||
|
||||
def test_invalid_barcode_still_uses_fixed(self, validator):
|
||||
"""条码验证失败但有修复值时仍使用修复值"""
|
||||
product = {
|
||||
'barcode': '5920584471055', # 5开头, 会被修正为6开头
|
||||
'price': 10.0,
|
||||
'quantity': 5,
|
||||
}
|
||||
result = validator.validate_product(product)
|
||||
assert result['barcode'] == '6920584471055'
|
||||
|
||||
def test_amount_zero_marks_gift(self, validator):
|
||||
"""金额为0时标记为赠品"""
|
||||
product = {
|
||||
'barcode': '6920584471055',
|
||||
'price': 10.0,
|
||||
'quantity': 5,
|
||||
'amount': 0,
|
||||
}
|
||||
result = validator.validate_product(product)
|
||||
assert result.get('is_gift') is True
|
||||
Reference in New Issue
Block a user