test(business): smoke test every query template
Add a shared Kingdee sales-order fixture and execute every schema-listed business template at least once, ensuring each returns JSON-serializable output. Keep existing exact assertions for representative totals, rankings, staff, status, and delivery-plan templates. Validation: python3 -m unittest discover -v; python3 -m compileall obsidian_wiki tests scripts; git diff --check
This commit is contained in:
+120
-83
@@ -28,6 +28,91 @@ class BusinessFactsTests(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.tempdir.cleanup()
|
self.tempdir.cleanup()
|
||||||
|
|
||||||
|
def seed_template_fixture(self):
|
||||||
|
sync_kingdee_orders(
|
||||||
|
self.vault,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "1001",
|
||||||
|
"billno": "SO-001",
|
||||||
|
"bizdate": "2026-06-03",
|
||||||
|
"customer_number": "C01",
|
||||||
|
"customer_name": "Customer A",
|
||||||
|
"totalallamount": "100.00",
|
||||||
|
"settlecurrency_number": "CNY",
|
||||||
|
"billstatus": "C",
|
||||||
|
"orderstatus": "D",
|
||||||
|
"closestatus": "A",
|
||||||
|
"org_name": "Org A",
|
||||||
|
"dept_name": "Dept A",
|
||||||
|
"operator_operatorname": "徐丽",
|
||||||
|
"billentry": [
|
||||||
|
{
|
||||||
|
"id": "L1",
|
||||||
|
"material_masterid_number": "P01",
|
||||||
|
"material_masterid_name": "Part 1",
|
||||||
|
"qty": "2",
|
||||||
|
"amountandtax": "60",
|
||||||
|
"project_name": "Project A",
|
||||||
|
"warehouse_name": "Warehouse A",
|
||||||
|
"orderdeliverentry": [
|
||||||
|
{
|
||||||
|
"id": "D1",
|
||||||
|
"d_plandate": "2026-06-10",
|
||||||
|
"d_plandeliverydate": "2026-06-08",
|
||||||
|
"d_planqty": "2",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "L2",
|
||||||
|
"material_masterid_number": "P02",
|
||||||
|
"material_masterid_name": "Part 2",
|
||||||
|
"qty": "1",
|
||||||
|
"amountandtax": "40",
|
||||||
|
"project_name": "Project B",
|
||||||
|
"warehouse_name": "Warehouse B",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1002",
|
||||||
|
"billno": "SO-002",
|
||||||
|
"bizdate": "2026-06-04",
|
||||||
|
"customer_number": "C02",
|
||||||
|
"customer_name": "Customer B",
|
||||||
|
"totalallamount": "300.00",
|
||||||
|
"settlecurrency_number": "CNY",
|
||||||
|
"billstatus": "B",
|
||||||
|
"orderstatus": "C",
|
||||||
|
"closestatus": "A",
|
||||||
|
"org_name": "Org B",
|
||||||
|
"dept_name": "Dept B",
|
||||||
|
"operator_operatorname": "王五",
|
||||||
|
"h5a3_basedatafield2_operatorname": "徐丽",
|
||||||
|
"billentry": [
|
||||||
|
{
|
||||||
|
"id": "L1",
|
||||||
|
"material_masterid_number": "P01",
|
||||||
|
"material_masterid_name": "Part 1",
|
||||||
|
"qty": "3",
|
||||||
|
"amountandtax": "300",
|
||||||
|
"project_name": "Project A",
|
||||||
|
"warehouse_name": "Warehouse A",
|
||||||
|
"orderdeliverentry": [
|
||||||
|
{
|
||||||
|
"id": "D2",
|
||||||
|
"d_plandate": "2026-06-11",
|
||||||
|
"d_planqty": "3",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synced_at="2026-07-06T09:00:00Z",
|
||||||
|
)
|
||||||
|
|
||||||
def test_monthly_order_count_deduplicates_order_lines(self):
|
def test_monthly_order_count_deduplicates_order_lines(self):
|
||||||
sync_kingdee_orders(
|
sync_kingdee_orders(
|
||||||
self.vault,
|
self.vault,
|
||||||
@@ -523,89 +608,7 @@ class BusinessFactsTests(unittest.TestCase):
|
|||||||
self.assertIn("raw_snapshots", schema["sql_guardrails"]["forbidden_tables"])
|
self.assertIn("raw_snapshots", schema["sql_guardrails"]["forbidden_tables"])
|
||||||
|
|
||||||
def test_execute_business_templates_cover_core_totals_rankings_lists_and_delivery_plans(self):
|
def test_execute_business_templates_cover_core_totals_rankings_lists_and_delivery_plans(self):
|
||||||
sync_kingdee_orders(
|
self.seed_template_fixture()
|
||||||
self.vault,
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"id": "1001",
|
|
||||||
"billno": "SO-001",
|
|
||||||
"bizdate": "2026-06-03",
|
|
||||||
"customer_number": "C01",
|
|
||||||
"customer_name": "Customer A",
|
|
||||||
"totalallamount": "100.00",
|
|
||||||
"settlecurrency_number": "CNY",
|
|
||||||
"billstatus": "C",
|
|
||||||
"orderstatus": "D",
|
|
||||||
"closestatus": "A",
|
|
||||||
"org_name": "Org A",
|
|
||||||
"dept_name": "Dept A",
|
|
||||||
"operator_operatorname": "徐丽",
|
|
||||||
"billentry": [
|
|
||||||
{
|
|
||||||
"id": "L1",
|
|
||||||
"material_masterid_number": "P01",
|
|
||||||
"material_masterid_name": "Part 1",
|
|
||||||
"qty": "2",
|
|
||||||
"amountandtax": "60",
|
|
||||||
"project_name": "Project A",
|
|
||||||
"warehouse_name": "Warehouse A",
|
|
||||||
"orderdeliverentry": [
|
|
||||||
{
|
|
||||||
"id": "D1",
|
|
||||||
"d_plandate": "2026-06-10",
|
|
||||||
"d_plandeliverydate": "2026-06-08",
|
|
||||||
"d_planqty": "2",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "L2",
|
|
||||||
"material_masterid_number": "P02",
|
|
||||||
"material_masterid_name": "Part 2",
|
|
||||||
"qty": "1",
|
|
||||||
"amountandtax": "40",
|
|
||||||
"project_name": "Project B",
|
|
||||||
"warehouse_name": "Warehouse B",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "1002",
|
|
||||||
"billno": "SO-002",
|
|
||||||
"bizdate": "2026-06-04",
|
|
||||||
"customer_number": "C02",
|
|
||||||
"customer_name": "Customer B",
|
|
||||||
"totalallamount": "300.00",
|
|
||||||
"settlecurrency_number": "CNY",
|
|
||||||
"billstatus": "B",
|
|
||||||
"orderstatus": "C",
|
|
||||||
"closestatus": "A",
|
|
||||||
"org_name": "Org B",
|
|
||||||
"dept_name": "Dept B",
|
|
||||||
"operator_operatorname": "王五",
|
|
||||||
"h5a3_basedatafield2_operatorname": "徐丽",
|
|
||||||
"billentry": [
|
|
||||||
{
|
|
||||||
"id": "L1",
|
|
||||||
"material_masterid_number": "P01",
|
|
||||||
"material_masterid_name": "Part 1",
|
|
||||||
"qty": "3",
|
|
||||||
"amountandtax": "300",
|
|
||||||
"project_name": "Project A",
|
|
||||||
"warehouse_name": "Warehouse A",
|
|
||||||
"orderdeliverentry": [
|
|
||||||
{
|
|
||||||
"id": "D2",
|
|
||||||
"d_plandate": "2026-06-11",
|
|
||||||
"d_planqty": "3",
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
synced_at="2026-07-06T09:00:00Z",
|
|
||||||
)
|
|
||||||
|
|
||||||
count = execute_business_template(self.db_path, "order_count", start_date="2026-06-01", end_date="2026-07-01")
|
count = execute_business_template(self.db_path, "order_count", start_date="2026-06-01", end_date="2026-07-01")
|
||||||
sales = execute_business_template(self.db_path, "sales_amount", year=2026, month=6)
|
sales = execute_business_template(self.db_path, "sales_amount", year=2026, month=6)
|
||||||
@@ -641,6 +644,40 @@ class BusinessFactsTests(unittest.TestCase):
|
|||||||
[("B", 1), ("C", 1)],
|
[("B", 1), ("C", 1)],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_all_business_templates_execute_with_fixture_data(self):
|
||||||
|
self.seed_template_fixture()
|
||||||
|
required_values = {
|
||||||
|
"customer": "Customer A",
|
||||||
|
"product": "P01",
|
||||||
|
"staff": "徐丽",
|
||||||
|
}
|
||||||
|
templates = business_schema()["templates"]
|
||||||
|
template_names = [template["name"] for template in templates]
|
||||||
|
|
||||||
|
for template in templates:
|
||||||
|
params = {
|
||||||
|
"year": 2026,
|
||||||
|
"month": 6,
|
||||||
|
"limit": 5,
|
||||||
|
"granularity": "day",
|
||||||
|
"staff_role_scope": "all",
|
||||||
|
}
|
||||||
|
for required in template["required_parameters"]:
|
||||||
|
params[required] = required_values[required]
|
||||||
|
|
||||||
|
with self.subTest(template=template["name"]):
|
||||||
|
result = execute_business_template(self.db_path, template["name"], **params)
|
||||||
|
self.assertEqual(result["template"], template["name"])
|
||||||
|
self.assertIsInstance(result["data"], dict)
|
||||||
|
self.assertIn("answer", result)
|
||||||
|
self.assertEqual(result["last_sync"], "2026-07-06T09:00:00Z")
|
||||||
|
self.assertIsInstance(result["is_stale"], bool)
|
||||||
|
if "rows" in result:
|
||||||
|
self.assertIsInstance(result["rows"], list)
|
||||||
|
json.dumps(result, ensure_ascii=False)
|
||||||
|
|
||||||
|
self.assertEqual(len(template_names), len(set(template_names)))
|
||||||
|
|
||||||
def test_execute_business_template_rejects_unknown_or_missing_parameters(self):
|
def test_execute_business_template_rejects_unknown_or_missing_parameters(self):
|
||||||
with self.assertRaisesRegex(RuntimeError, "unknown business template"):
|
with self.assertRaisesRegex(RuntimeError, "unknown business template"):
|
||||||
execute_business_template(self.db_path, "not_a_template", year=2026, month=6)
|
execute_business_template(self.db_path, "not_a_template", year=2026, month=6)
|
||||||
|
|||||||
Reference in New Issue
Block a user