1
0
forked from erp-dev/erp
Files
erpnew/api_v1/test_mingdaoyun_utils.py

390 lines
15 KiB
Python
Raw 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.
import asyncio
from decimal import Decimal
from unittest.mock import patch
from django.test import SimpleTestCase
from flower.utils.mingdaoyun import mappings
from flower.utils.mingdaoyun.client import MingDaoYunClient
from flower.utils.mingdaoyun.fetch import (
fetch_customers_from_mingdaoyun,
fetch_plate_orders_from_mingdaoyun,
fetch_products_from_mingdaoyun,
fetch_row_by_rowid_from_mingdaoyun,
sync_fabric_from_mingdaoyun,
)
from flower.utils.mingdaoyun.models import Customer, Fabric, Product
from flower.utils.mingdaoyun.parsers import pick_customer, pick_fabric, pick_product
class _FakeMDYClient:
def __init__(self, response):
self.response = response
self.calls = []
async def post(self, endpoint, data=None, params=None, headers=None):
self.calls.append(
{
"endpoint": endpoint,
"data": data,
"params": params,
"headers": headers,
}
)
return self.response
class _AsyncCM:
def __init__(self, obj):
self.obj = obj
async def __aenter__(self):
return self.obj
async def __aexit__(self, exc_type, exc, tb):
return False
class _FakeAiohttpResponse:
def __init__(self, json_body):
self._json_body = json_body
self.raise_for_status_called = False
def raise_for_status(self):
self.raise_for_status_called = True
async def json(self):
return self._json_body
class _FakeAiohttpSession:
def __init__(self):
self.post_calls = []
def post(self, url, json=None):
resp = _FakeAiohttpResponse({"ok": True})
self.post_calls.append({"url": url, "json": json, "response": resp})
return _AsyncCM(resp)
class _FakeAiohttpClientSession:
def __init__(self, *args, **kwargs):
self.headers = kwargs.get("headers")
self.session = _FakeAiohttpSession()
async def __aenter__(self):
return self.session
async def __aexit__(self, exc_type, exc, tb):
return False
class MingDaoYunParsersTestCase(SimpleTestCase):
def test_pick_product_maps_fields_and_converts_numbers(self):
row = {
mappings.product_type_map["uid"]: "P-001",
mappings.product_type_map["rowid"]: "row-1",
mappings.product_type_map["created_at"]: "2025-12-01 00:00:00",
mappings.product_type_map["name"]: "产品A",
mappings.product_type_map["pieces"]: "12",
mappings.product_type_map["segment_size"]: "3",
mappings.product_type_map["unit"]: "",
mappings.product_type_map["color"]: "",
mappings.product_type_map["detail_str"]: '{"k": 1}',
}
product = pick_product(row)
self.assertIsInstance(product, Product)
self.assertEqual(product.uid, "P-001")
self.assertEqual(product.rowid, "row-1")
self.assertEqual(product.created_at, "2025-12-01 00:00:00")
self.assertEqual(product.name, "产品A")
self.assertEqual(product.pieces, 12)
self.assertEqual(product.segment_size, Decimal("3"))
# width 与 segment_size 当前共用同一个 controlId保持同步代码现状
self.assertEqual(product.width, "3")
self.assertEqual(product.unit, "")
self.assertEqual(product.color, "")
self.assertEqual(product.detail, {"k": 1})
def test_pick_product_invalid_numbers_become_none(self):
row = {
mappings.product_type_map["uid"]: "P-002",
mappings.product_type_map["rowid"]: "row-2",
mappings.product_type_map["created_at"]: "2025-12-01 00:00:00",
mappings.product_type_map["name"]: "产品B",
mappings.product_type_map["pieces"]: "invalid",
mappings.product_type_map["segment_size"]: "",
}
product = pick_product(row)
self.assertIsNone(product.pieces)
self.assertIsNone(product.segment_size)
def test_pick_customer_maps_fields(self):
row = {
mappings.customer_type_map["uid"]: "C-001",
mappings.customer_type_map["rowid"]: "crow-1",
mappings.customer_type_map["created_at"]: "2025-12-01 00:00:00",
mappings.customer_type_map["name"]: "客户甲",
mappings.customer_type_map["area"]: "杭州",
}
customer = pick_customer(row)
self.assertIsInstance(customer, Customer)
self.assertEqual(customer.uid, "C-001")
self.assertEqual(customer.rowid, "crow-1")
self.assertEqual(customer.created_at, "2025-12-01 00:00:00")
self.assertEqual(customer.name, "客户甲")
self.assertEqual(customer.area, "杭州")
def test_pick_fabric_maps_fields(self):
row = {
mappings.fabric_type_map["name"]: "120克本白四面弹",
}
fabric = pick_fabric(row)
self.assertIsInstance(fabric, Fabric)
self.assertEqual(fabric.name, "120克本白四面弹")
class MingDaoYunFetchTestCase(SimpleTestCase):
def test_fetch_products_calls_client_with_expected_payload_and_parses(self):
response = {
"data": {
"rows": [
{
mappings.product_type_map["uid"]: "P-001",
mappings.product_type_map["rowid"]: "row-1",
mappings.product_type_map["created_at"]: "2025-12-01 00:00:00",
mappings.product_type_map["name"]: "产品A",
mappings.product_type_map["pieces"]: "1",
mappings.product_type_map["segment_size"]: "2",
}
],
"total": 123,
}
}
fake_client = _FakeMDYClient(response)
with patch("flower.utils.mingdaoyun.fetch.get_default_mingdaoyun_client", return_value=fake_client):
products, total = asyncio.run(fetch_products_from_mingdaoyun(page=2, page_size=5))
self.assertEqual(total, 123)
self.assertEqual(len(products), 1)
self.assertIsInstance(products[0], Product)
self.assertEqual(products[0].uid, "P-001")
self.assertEqual(len(fake_client.calls), 1)
call = fake_client.calls[0]
self.assertEqual(call["endpoint"], "/v2/open/worksheet/getFilterRows")
self.assertEqual(call["data"]["worksheetId"], mappings.MDY_WORKSHEET_ID_PRODUCT)
self.assertEqual(call["data"]["pageIndex"], 2)
self.assertEqual(call["data"]["pageSize"], 5)
self.assertEqual(call["data"]["sortId"], "ctime")
self.assertIs(call["data"]["isAsc"], True)
def test_fetch_customers_calls_client_with_expected_payload_and_parses(self):
response = {
"data": {
"rows": [
{
mappings.customer_type_map["uid"]: "C-001",
mappings.customer_type_map["rowid"]: "crow-1",
mappings.customer_type_map["created_at"]: "2025-12-01 00:00:00",
mappings.customer_type_map["name"]: "客户甲",
}
],
"total": 9,
}
}
fake_client = _FakeMDYClient(response)
with patch("flower.utils.mingdaoyun.fetch.get_default_mingdaoyun_client", return_value=fake_client):
customers, total = asyncio.run(fetch_customers_from_mingdaoyun(page=3, page_size=7))
self.assertEqual(total, 9)
self.assertEqual(len(customers), 1)
self.assertIsInstance(customers[0], Customer)
self.assertEqual(customers[0].uid, "C-001")
call = fake_client.calls[0]
self.assertEqual(call["endpoint"], "/v2/open/worksheet/getFilterRows")
self.assertEqual(call["data"]["worksheetId"], mappings.MDY_WORKSHEET_ID_CUSTOMER)
self.assertEqual(call["data"]["pageIndex"], 3)
self.assertEqual(call["data"]["pageSize"], 7)
def test_fetch_plate_orders_returns_raw_rows_and_total(self):
rows = [{"rowid": "r1", "ctime": "2025-12-17 10:07:00"}]
response = {"data": {"rows": rows, "total": 100}}
fake_client = _FakeMDYClient(response)
with patch("flower.utils.mingdaoyun.fetch.get_default_mingdaoyun_client", return_value=fake_client):
got_rows, total = asyncio.run(
fetch_plate_orders_from_mingdaoyun(page=4, page_size=11, sort_id="ctime", is_asc=False)
)
self.assertEqual(total, 100)
self.assertEqual(got_rows, rows)
call = fake_client.calls[0]
self.assertEqual(call["data"]["worksheetId"], mappings.MDY_WORKSHEET_ID_PLATE_ORDER)
self.assertEqual(call["data"]["pageIndex"], 4)
self.assertEqual(call["data"]["pageSize"], 11)
self.assertEqual(call["data"]["sortId"], "ctime")
self.assertIs(call["data"]["isAsc"], False)
def test_fetch_products_returns_empty_when_no_data(self):
fake_client = _FakeMDYClient({})
with patch("flower.utils.mingdaoyun.fetch.get_default_mingdaoyun_client", return_value=fake_client):
products, total = asyncio.run(fetch_products_from_mingdaoyun(page=1, page_size=1))
self.assertEqual(products, [])
self.assertEqual(total, 0)
def test_fetch_row_by_rowid_builds_filters_payload_and_returns_first_row(self):
rowid = "row-xyz"
row = {
"rowid": rowid,
mappings.product_type_map["uid"]: "P-ROW",
}
response = {"data": {"rows": [row]}}
fake_client = _FakeMDYClient(response)
with patch("flower.utils.mingdaoyun.fetch.get_default_mingdaoyun_client", return_value=fake_client):
got = asyncio.run(
fetch_row_by_rowid_from_mingdaoyun(
worksheet_id=mappings.MDY_WORKSHEET_ID_PRODUCT,
rowid=rowid,
)
)
self.assertEqual(got, row)
self.assertEqual(len(fake_client.calls), 1)
call = fake_client.calls[0]
self.assertEqual(call["endpoint"], "/v2/open/worksheet/getFilterRows")
self.assertEqual(call["data"]["worksheetId"], mappings.MDY_WORKSHEET_ID_PRODUCT)
self.assertEqual(call["data"]["listType"], 1)
self.assertEqual(call["data"]["sortId"], "ctime")
self.assertIs(call["data"]["isAsc"], False)
self.assertIs(call["data"]["notGetTotal"], True)
self.assertEqual(
call["data"]["filters"],
[
{
"controlId": "rowId",
"dataType": 2,
"filterType": 3,
"value": rowid,
}
],
)
def test_fetch_row_by_rowid_returns_none_when_no_data(self):
fake_client = _FakeMDYClient({})
with patch("flower.utils.mingdaoyun.fetch.get_default_mingdaoyun_client", return_value=fake_client):
got = asyncio.run(
fetch_row_by_rowid_from_mingdaoyun(
worksheet_id=mappings.MDY_WORKSHEET_ID_PRODUCT,
rowid="missing",
)
)
self.assertIsNone(got)
class MingDaoYunFabricSyncTestCase(SimpleTestCase):
def test_sync_fabric_calls_create_hook_and_returns_total(self):
response = {
"data": {
"rows": [
{mappings.fabric_type_map["name"]: "布料A"},
{mappings.fabric_type_map["name"]: "布料B"},
],
"total": 2,
}
}
fake_client = _FakeMDYClient(response)
called = {}
def fake_create(page, page_size, fabrics):
called["page"] = page
called["page_size"] = page_size
called["fabrics"] = fabrics
def fake_sync_to_async(func):
async def _wrapper(*args, **kwargs):
return func(*args, **kwargs)
return _wrapper
with (
patch("flower.utils.mingdaoyun.fetch.get_default_mingdaoyun_client", return_value=fake_client),
patch("flower.utils.mingdaoyun.fetch._create_fabric_quick_inputs", side_effect=fake_create),
patch("flower.utils.mingdaoyun.fetch.sync_to_async", side_effect=fake_sync_to_async),
):
total = asyncio.run(sync_fabric_from_mingdaoyun(page=9, page_size=10))
self.assertEqual(total, 2)
self.assertEqual(called["page"], 9)
self.assertEqual(called["page_size"], 10)
self.assertEqual([f.name for f in called["fabrics"]], ["布料A", "布料B"])
def test_sync_fabric_returns_zero_when_no_data(self):
fake_client = _FakeMDYClient({})
called = {"hit": False}
def fake_create(*_args, **_kwargs):
called["hit"] = True
def fake_sync_to_async(func):
async def _wrapper(*args, **kwargs):
return func(*args, **kwargs)
return _wrapper
with (
patch("flower.utils.mingdaoyun.fetch.get_default_mingdaoyun_client", return_value=fake_client),
patch("flower.utils.mingdaoyun.fetch._create_fabric_quick_inputs", side_effect=fake_create),
patch("flower.utils.mingdaoyun.fetch.sync_to_async", side_effect=fake_sync_to_async),
):
total = asyncio.run(sync_fabric_from_mingdaoyun(page=1, page_size=1))
self.assertEqual(total, 0)
self.assertIs(called["hit"], False)
class MingDaoYunClientTestCase(SimpleTestCase):
def test_client_post_merges_auth_params_and_passes_headers(self):
created = {}
def fake_client_session(*args, **kwargs):
cs = _FakeAiohttpClientSession(*args, **kwargs)
created["cs"] = cs
return cs
client = MingDaoYunClient(app_key="ak", sign="sg", base_url="https://api.mingdao.com")
with patch("flower.utils.mingdaoyun.client.aiohttp.ClientSession", side_effect=fake_client_session):
body = asyncio.run(
client.post(
endpoint="/v2/open/worksheet/getFilterRows",
data={"worksheetId": "dummy", "pageIndex": 1},
headers={"X-Test": "1"},
)
)
self.assertEqual(body, {"ok": True})
cs = created["cs"]
self.assertIsInstance(cs.headers, dict)
self.assertEqual(cs.headers.get("Content-Type"), "application/json")
self.assertEqual(cs.headers.get("X-Test"), "1")
self.assertEqual(len(cs.session.post_calls), 1)
call = cs.session.post_calls[0]
self.assertEqual(call["url"], "https://api.mingdao.com/v2/open/worksheet/getFilterRows")
self.assertEqual(call["json"]["worksheetId"], "dummy")
self.assertEqual(call["json"]["pageIndex"], 1)
self.assertEqual(call["json"]["appKey"], "ak")
self.assertEqual(call["json"]["sign"], "sg")
self.assertTrue(call["response"].raise_for_status_called)