dingdanquanliucheng/backend/tests/test_pricing.py

667 lines
30 KiB
Python
Raw Normal View History

2026-06-19 23:03:14 +08:00
"""定价引擎测试 — 覆盖报价计算、定价规则CRUD、供应商成本、价格层级。
测试用例:
- PRICE-001 ~ PRICE-007: 定价规则 CRUD
- PRICE-010 ~ PRICE-020: 报价计算面积/重量/按件/按米附加费条件公式
- PRICE-030 ~ PRICE-033: 供应商成本 CRUD
- PRICE-040 ~ PRICE-043: 价格层级 CRUD
"""
from __future__ import annotations
import json
import pytest
# ============================================================
# 定价引擎单元测试(直接调用 PricingEngine不走 API
# ============================================================
@pytest.mark.pricing
class TestPricingEngineUnit:
"""定价引擎核心计算逻辑的单元测试。"""
def _make_rule(self, **overrides):
"""创建模拟定价规则对象。"""
class FakeRule:
pass
rule = FakeRule()
rule.base_unit_price = overrides.get("base_unit_price", 50.0)
rule.pricing_type = overrides.get("pricing_type", "area")
rule.pricing_unit = overrides.get("pricing_unit", "")
rule.pricing_inputs = overrides.get("pricing_inputs", json.dumps([
{"key": "length", "type": "number", "default_unit": "m"},
{"key": "width", "type": "number", "default_unit": "m"},
]))
rule.formula_expr = overrides.get("formula_expr", "$input.length_m * $input.width_m * $rule.base_unit_price")
rule.formula_constants = overrides.get("formula_constants", None)
rule.surcharge_json = overrides.get("surcharge_json", None)
rule.formula_note = overrides.get("formula_note", None)
rule.tax_rate = overrides.get("tax_rate", 0)
rule.tax_inclusive = overrides.get("tax_inclusive", 0)
return rule
def test_area_pricing_basic(self):
"""PRICE-010: 面积定价 — 2m x 1.2m x ¥50/㎡ = ¥120"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1.2})
assert result["base_cost"] == 120.0
assert result["cost_price"] == 120.0
assert result["total_surcharge"] == 0
def test_area_pricing_cm_unit(self):
"""单位换算: cm → m, 200cm x 120cm x ¥50/㎡ = ¥120"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0)
result = pricing_engine.calculate(rule, {
"length": 200, "length_unit": "cm",
"width": 120, "width_unit": "cm",
})
assert result["base_cost"] == 120.0
def test_area_pricing_mm_unit(self):
"""单位换算: mm → m, 2000mm x 1200mm x ¥50/㎡ = ¥120"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0)
result = pricing_engine.calculate(rule, {
"length": 2000, "length_unit": "mm",
"width": 1200, "width_unit": "mm",
})
assert result["base_cost"] == 120.0
def test_formula_with_constants(self):
"""PRICE-019: 含常量的公式计算, $const.rate 作为乘数"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
formula_expr="$input.length_m * $input.width_m * $rule.base_unit_price * $const.rate",
formula_constants=json.dumps({"rate": 1.5}),
)
# 1*1*50*1.5 = 75
result = pricing_engine.calculate(rule, {"length": 1, "width": 1})
assert result["base_cost"] == 75.0
def test_surcharge_per_sqm(self):
"""PRICE-012: 按面积附加费, 2m*1.2m=2.4㎡, ¥10/㎡ = ¥24"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}),
)
result = pricing_engine.calculate(rule, {
"length": 2, "width": 1.2, "surcharge_urgent": True,
})
assert result["total_surcharge"] == 24.0
assert len(result["surcharge_items"]) == 1
assert result["surcharge_items"][0]["key"] == "urgent"
def test_surcharge_per_piece(self):
"""PRICE-013: 按件附加费, 10件 x ¥5/件 = ¥50"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"packaging": {"name": "包装费", "method": "per_piece", "price": 5, "input_key": "qty"}
}),
)
result = pricing_engine.calculate(rule, {
"length": 2, "width": 1.2, "qty": 10, "surcharge_packaging": True,
})
assert result["total_surcharge"] == 50.0
def test_surcharge_per_linear_m(self):
"""PRICE-014: 按米附加费, 20m x ¥3/m = ¥60"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"edge": {"name": "包边费", "method": "per_linear_m", "price": 3, "input_key": "edge_length"}
}),
)
result = pricing_engine.calculate(rule, {
"length": 2, "width": 1.2, "edge_length": 20, "surcharge_edge": True,
})
assert result["total_surcharge"] == 60.0
def test_surcharge_not_enabled(self):
"""未启用的附加费不计入"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}),
)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1.2})
assert result["total_surcharge"] == 0
assert len(result["surcharge_items"]) == 0
def test_tax_exclusive(self):
"""PRICE-017: 不含税定价, cost=100, tax_rate=13%, tax_amount=13, price_in_tax=113"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0, tax_rate=13, tax_inclusive=0)
# 2m x 1m x 50 = 100
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["cost_price"] == 100.0
assert result["tax_rate"] == 13
assert result["price_ex_tax"] == 100.0
assert result["tax_amount"] == 13.0
assert result["price_in_tax"] == 113.0
def test_tax_inclusive(self):
"""PRICE-016: 含税定价, cost=113(含税), tax_rate=13%, price_ex_tax≈100"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=56.5, tax_rate=13, tax_inclusive=1)
# 2m x 1m x 56.5 = 113
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["cost_price"] == 113.0
assert result["price_in_tax"] == 113.0
assert result["price_ex_tax"] == 100.0
assert result["tax_amount"] == 13.0
def test_no_tax(self):
"""无税率时, tax_amount=0, price_in_tax=cost_price"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0, tax_rate=0)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["tax_amount"] == 0
assert result["price_in_tax"] == result["cost_price"]
def test_empty_inputs(self):
"""空输入返回 0"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule()
result = pricing_engine.calculate(rule, {})
assert result["base_cost"] == 0.0
def test_invalid_formula_returns_zero(self):
"""无效公式返回 0, 不抛异常"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(formula_expr="$nonexistent.field * ???")
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["base_cost"] == 0.0
def test_formula_detail_text(self):
"""公式说明包含关键信息"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1.2})
assert "2.0m" in result["formula_detail"]
assert "1.2m" in result["formula_detail"]
assert "120.00" in result["formula_detail"]
def test_formula_detail_with_surcharge(self):
"""公式说明包含附加费项"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}),
)
result = pricing_engine.calculate(rule, {
"length": 2, "width": 1.2, "surcharge_urgent": True,
})
assert "加急费" in result["formula_detail"]
def test_formula_detail_with_tax(self):
"""公式说明包含税率信息"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(base_unit_price=50.0, tax_rate=13, tax_inclusive=0)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert "13" in result["formula_detail"]
assert "" in result["formula_detail"]
def test_get_surcharge_options(self):
"""附加费选项列表"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10},
"pack": {"name": "包装费", "method": "per_piece", "price": 5, "input_key": "qty"},
}))
options = pricing_engine.get_available_surcharge_options(rule)
assert len(options) == 2
assert options[0]["key"] == "urgent"
assert options[1]["key"] == "pack"
def test_conditional_formula(self):
"""条件公式: ${1 > 0} ? 100 : 50 → 100"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
formula_expr="${$input.length_m > 1} ? $input.length_m * $rule.base_unit_price : $rule.base_unit_price",
)
result = pricing_engine.calculate(rule, {"length": 2, "width": 1})
assert result["base_cost"] == 100.0
def test_conditional_formula_false_branch(self):
"""条件公式假分支: length=0.5 <= 1, 走 50"""
from backend.app.services.pricing_engine import pricing_engine
rule = self._make_rule(
formula_expr="${$input.length_m > 1} ? $input.length_m * $rule.base_unit_price : $rule.base_unit_price",
)
result = pricing_engine.calculate(rule, {"length": 0.5, "width": 1})
assert result["base_cost"] == 50.0
# ============================================================
# 定价规则 CRUD API 测试
# ============================================================
@pytest.mark.pricing
@pytest.mark.p1
class TestPricingRuleCRUD:
"""PRICE-001 ~ PRICE-007: 定价规则 API 测试。"""
def _create_rule(self, client, headers, product_id=1, **overrides):
"""辅助: 创建定价规则。"""
payload = {
"product_id": product_id,
"product_name": f"产品{product_id}",
"pricing_type": "area",
"base_unit_price": 50.0,
"pricing_inputs": json.dumps([
{"key": "length", "type": "number", "default_unit": "m"},
{"key": "width", "type": "number", "default_unit": "m"},
]),
"formula_expr": "$input.length_m * $input.width_m * $rule.base_unit_price",
}
payload.update(overrides)
resp = client.post("/api/pricing-rules", headers=headers, json=payload)
return resp
def test_create_pricing_rule(self, client, admin_headers, make_product):
"""PRICE-001: 创建面积定价规则。"""
product = make_product()
resp = self._create_rule(client, admin_headers, product_id=product.id)
assert resp.status_code == 200
data = resp.json()
assert data["code"] == 0
assert data["data"]["product_id"] == product.id
assert data["data"]["pricing_type"] == "area"
assert data["data"]["base_unit_price"] == 50.0
def test_create_duplicate_rule_rejected(self, client, admin_headers, make_product):
"""PRICE-001b: 同产品重复创建应拒绝。"""
product = make_product()
self._create_rule(client, admin_headers, product_id=product.id)
resp = self._create_rule(client, admin_headers, product_id=product.id)
assert resp.status_code == 400
def test_list_pricing_rules(self, client, admin_headers, make_product):
"""PRICE-007: 查询定价规则列表。"""
product = make_product()
self._create_rule(client, admin_headers, product_id=product.id)
resp = client.get("/api/pricing-rules", headers=admin_headers)
assert resp.status_code == 200
data = resp.json()
assert data["data"]["total"] >= 1
def test_list_pricing_rules_by_product(self, client, admin_headers, make_product):
"""按产品ID筛选定价规则。"""
product = make_product()
self._create_rule(client, admin_headers, product_id=product.id)
resp = client.get(f"/api/pricing-rules?product_id={product.id}", headers=admin_headers)
assert resp.status_code == 200
assert resp.json()["data"]["total"] >= 1
def test_get_pricing_rule(self, client, admin_headers, make_product):
"""按产品ID获取定价规则。"""
product = make_product()
self._create_rule(client, admin_headers, product_id=product.id)
resp = client.get(f"/api/pricing-rules/{product.id}", headers=admin_headers)
assert resp.status_code == 200
assert resp.json()["data"]["product_id"] == product.id
def test_get_pricing_rule_not_found(self, client, admin_headers):
"""查询不存在的产品定价规则返回 404。"""
resp = client.get("/api/pricing-rules/99999", headers=admin_headers)
assert resp.status_code == 404
def test_update_pricing_rule(self, client, admin_headers, make_product):
"""PRICE-005: 更新定价规则。"""
product = make_product()
create_resp = self._create_rule(client, admin_headers, product_id=product.id)
rule_id = create_resp.json()["data"]["id"]
resp = client.put(f"/api/pricing-rules/{rule_id}", headers=admin_headers, json={
"base_unit_price": 80.0,
})
assert resp.status_code == 200
assert resp.json()["data"]["base_unit_price"] == 80.0
def test_update_pricing_rule_not_found(self, client, admin_headers):
"""更新不存在的定价规则返回 404。"""
resp = client.put("/api/pricing-rules/99999", headers=admin_headers, json={
"base_unit_price": 80.0,
})
assert resp.status_code == 404
def test_delete_pricing_rule(self, client, admin_headers, make_product):
"""PRICE-006: 删除定价规则(软删除)。"""
product = make_product()
create_resp = self._create_rule(client, admin_headers, product_id=product.id)
rule_id = create_resp.json()["data"]["id"]
resp = client.delete(f"/api/pricing-rules/{rule_id}", headers=admin_headers)
assert resp.status_code == 200
assert resp.json()["data"]["deleted"] is True
# 删除后查询应返回 404
resp = client.get(f"/api/pricing-rules/{product.id}", headers=admin_headers)
assert resp.status_code == 404
def test_salesman_cannot_create_rule(self, client, salesman_headers, make_product):
"""业务员不能创建定价规则。"""
product = make_product()
resp = self._create_rule(client, salesman_headers, product_id=product.id)
assert resp.status_code == 403
def test_salesman_cannot_update_rule(self, client, salesman_headers, admin_headers, make_product):
"""业务员不能更新定价规则。"""
product = make_product()
create_resp = self._create_rule(client, admin_headers, product_id=product.id)
rule_id = create_resp.json()["data"]["id"]
resp = client.put(f"/api/pricing-rules/{rule_id}", headers=salesman_headers, json={
"base_unit_price": 80.0,
})
assert resp.status_code == 403
# ============================================================
# 报价计算 API 测试
# ============================================================
@pytest.mark.pricing
@pytest.mark.p1
class TestQuotationCalculation:
"""PRICE-010 ~ PRICE-020: 报价计算 API 测试。"""
def _setup_rule(self, client, headers, db_session, product, **overrides):
"""辅助: 在数据库中创建定价规则。"""
from backend.app.models.business import ProductPricingRule
defaults = {
"product_id": product.id,
"product_name": product.product_name,
"pricing_type": "area",
"base_unit_price": 50.0,
"pricing_unit": "",
"pricing_inputs": json.dumps([
{"key": "length", "type": "number", "default_unit": "m"},
{"key": "width", "type": "number", "default_unit": "m"},
]),
"formula_expr": "$input.length_m * $input.width_m * $rule.base_unit_price",
"tax_rate": 0,
"tax_inclusive": 0,
"status": 1,
"deleted": 0,
}
defaults.update(overrides)
rule = ProductPricingRule(**defaults)
db_session.add(rule)
db_session.flush()
return rule
def test_calculate_area(self, client, admin_headers, db_session, make_product):
"""PRICE-010: 面积报价计算, 2m x 1.2m x ¥50 = ¥120。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product, base_unit_price=50.0)
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1.2},
})
assert resp.status_code == 200
data = resp.json()["data"]
assert data["base_cost"] == 120.0
assert data["cost_price"] == 120.0
def test_calculate_with_surcharge(self, client, admin_headers, db_session, make_product):
"""报价计算含附加费。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product,
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}))
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1.2},
"surcharge_selections": {"surcharge_urgent": True},
})
assert resp.status_code == 200
data = resp.json()["data"]
assert data["total_surcharge"] == 24.0
assert data["cost_price"] == 144.0
def test_calculate_with_tax_exclusive(self, client, admin_headers, db_session, make_product):
"""PRICE-017: 不含税报价计算, 100 + 13% = 113。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product,
base_unit_price=50.0, tax_rate=13, tax_inclusive=0)
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
data = resp.json()["data"]
assert data["tax_rate"] == 13
assert data["price_ex_tax"] == 100.0
assert data["tax_amount"] == 13.0
assert data["price_in_tax"] == 113.0
def test_calculate_with_tax_inclusive(self, client, admin_headers, db_session, make_product):
"""PRICE-016: 含税报价计算。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product,
base_unit_price=56.5, tax_rate=13, tax_inclusive=1)
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
data = resp.json()["data"]
assert data["price_in_tax"] == 113.0
assert data["price_ex_tax"] == 100.0
def test_calculate_no_rule_returns_404(self, client, admin_headers, make_product):
"""PRICE-020: 无定价规则的产品返回 404。"""
product = make_product()
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
assert resp.status_code == 404
def test_calculate_with_price_tiers(self, client, admin_headers, db_session, make_product):
"""PRICE-018: 含价格层级的报价。"""
from backend.app.models.business import ProductPriceTier
product = make_product()
self._setup_rule(client, admin_headers, db_session, product, base_unit_price=50.0)
tier = ProductPriceTier(
product_id=product.id, tier_code="vip", tier_name="VIP",
price=45.0, price_unit="", status=1, deleted=0,
)
db_session.add(tier)
db_session.flush()
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
data = resp.json()["data"]
assert "vip" in data["sale_price_tier"]
assert data["sale_price_tier"]["vip"] == 45.0
def test_calculate_formula_detail(self, client, admin_headers, db_session, make_product):
"""报价结果包含公式说明。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product, base_unit_price=50.0)
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1.2},
})
data = resp.json()["data"]
assert "formula_detail" in data
assert "120.00" in data["formula_detail"]
def test_calculate_available_surcharge_options(self, client, admin_headers, db_session, make_product):
"""报价结果包含可用附加费选项。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product,
base_unit_price=50.0,
surcharge_json=json.dumps({
"urgent": {"name": "加急费", "method": "per_sqm", "price": 10}
}))
resp = client.post("/api/quotation/calculate", headers=admin_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
data = resp.json()["data"]
assert len(data["available_surcharge_options"]) == 1
assert data["available_surcharge_options"][0]["key"] == "urgent"
def test_salesman_can_calculate(self, client, salesman_headers, admin_headers, db_session, make_product):
"""业务员可以调用报价计算。"""
product = make_product()
self._setup_rule(client, admin_headers, db_session, product, base_unit_price=50.0)
resp = client.post("/api/quotation/calculate", headers=salesman_headers, json={
"product_id": product.id,
"user_inputs": {"length": 2, "width": 1},
})
assert resp.status_code == 200
# ============================================================
# 供应商成本 CRUD API 测试
# ============================================================
@pytest.mark.pricing
@pytest.mark.p1
class TestSupplierCostCRUD:
"""PRICE-030 ~ PRICE-033: 供应商成本 API 测试。"""
def _create_cost(self, client, headers, product_id, supplier_id, **overrides):
payload = {
"product_id": product_id,
"supplier_id": supplier_id,
"cost_price": 35.0,
"cost_unit": "",
}
payload.update(overrides)
return client.post("/api/supplier-costs", headers=headers, json=payload)
def test_create_supplier_cost(self, client, admin_headers, make_product, make_supplier):
"""PRICE-030: 创建供应商成本。"""
product = make_product()
supplier = make_supplier()
resp = self._create_cost(client, admin_headers, product.id, supplier.id)
assert resp.status_code == 200
assert resp.json()["data"]["cost_price"] == 35.0
def test_list_supplier_costs(self, client, admin_headers, make_product, make_supplier):
"""PRICE-031: 查询供应商成本列表。"""
product = make_product()
supplier = make_supplier()
self._create_cost(client, admin_headers, product.id, supplier.id)
resp = client.get(f"/api/supplier-costs?product_id={product.id}", headers=admin_headers)
assert resp.status_code == 200
assert len(resp.json()["data"]["list"]) >= 1
def test_update_supplier_cost(self, client, admin_headers, make_product, make_supplier):
"""PRICE-032: 更新供应商成本。"""
product = make_product()
supplier = make_supplier()
create_resp = self._create_cost(client, admin_headers, product.id, supplier.id)
cost_id = create_resp.json()["data"]["id"]
resp = client.put(f"/api/supplier-costs/{cost_id}", headers=admin_headers, json={
"cost_price": 40.0,
})
assert resp.status_code == 200
assert resp.json()["data"]["cost_price"] == 40.0
def test_delete_supplier_cost(self, client, admin_headers, make_product, make_supplier):
"""PRICE-033: 删除供应商成本(软删除)。"""
product = make_product()
supplier = make_supplier()
create_resp = self._create_cost(client, admin_headers, product.id, supplier.id)
cost_id = create_resp.json()["data"]["id"]
resp = client.delete(f"/api/supplier-costs/{cost_id}", headers=admin_headers)
assert resp.status_code == 200
# 删除后列表应为空
resp = client.get(f"/api/supplier-costs?product_id={product.id}", headers=admin_headers)
assert len(resp.json()["data"]["list"]) == 0
def test_salesman_cannot_create_cost(self, client, salesman_headers, make_product, make_supplier):
"""业务员不能创建供应商成本。"""
product = make_product()
supplier = make_supplier()
resp = self._create_cost(client, salesman_headers, product.id, supplier.id)
assert resp.status_code == 403
# ============================================================
# 价格层级 CRUD API 测试
# ============================================================
@pytest.mark.pricing
@pytest.mark.p1
class TestPriceTierCRUD:
"""PRICE-040 ~ PRICE-043: 价格层级 API 测试。"""
def _create_tier(self, client, headers, product_id, **overrides):
payload = {
"product_id": product_id,
"tier_code": "vip",
"tier_name": "VIP客户",
"price": 45.0,
"price_unit": "",
}
payload.update(overrides)
return client.post("/api/price-tiers", headers=headers, json=payload)
def test_create_price_tier(self, client, admin_headers, make_product):
"""PRICE-040: 创建价格层级。"""
product = make_product()
resp = self._create_tier(client, admin_headers, product.id)
assert resp.status_code == 200
assert resp.json()["data"]["tier_code"] == "vip"
assert resp.json()["data"]["price"] == 45.0
def test_list_price_tiers(self, client, admin_headers, make_product):
"""PRICE-041: 查询价格层级列表。"""
product = make_product()
self._create_tier(client, admin_headers, product.id)
resp = client.get(f"/api/price-tiers?product_id={product.id}", headers=admin_headers)
assert resp.status_code == 200
assert len(resp.json()["data"]["list"]) >= 1
def test_update_price_tier(self, client, admin_headers, make_product):
"""PRICE-042: 更新价格层级。"""
product = make_product()
create_resp = self._create_tier(client, admin_headers, product.id)
tier_id = create_resp.json()["data"]["id"]
resp = client.put(f"/api/price-tiers/{tier_id}", headers=admin_headers, json={
"price": 42.0,
})
assert resp.status_code == 200
assert resp.json()["data"]["price"] == 42.0
def test_delete_price_tier(self, client, admin_headers, make_product):
"""PRICE-043: 删除价格层级(软删除)。"""
product = make_product()
create_resp = self._create_tier(client, admin_headers, product.id)
tier_id = create_resp.json()["data"]["id"]
resp = client.delete(f"/api/price-tiers/{tier_id}", headers=admin_headers)
assert resp.status_code == 200
# 删除后列表应为空
resp = client.get(f"/api/price-tiers?product_id={product.id}", headers=admin_headers)
assert len(resp.json()["data"]["list"]) == 0
def test_salesman_can_list_tiers(self, client, salesman_headers, admin_headers, make_product):
"""业务员可以查看价格层级。"""
product = make_product()
self._create_tier(client, admin_headers, product.id)
resp = client.get(f"/api/price-tiers?product_id={product.id}", headers=salesman_headers)
assert resp.status_code == 200
def test_salesman_cannot_create_tier(self, client, salesman_headers, make_product):
"""业务员不能创建价格层级。"""
product = make_product()
resp = self._create_tier(client, salesman_headers, product.id)
assert resp.status_code == 403