import json from unittest.mock import patch import pytest from finance.formula_columns import available_metrics, ensure_available_metrics from finance.formula_views import design_column pytestmark = pytest.mark.django_db def model_response(design): return type( "Response", (), { "raise_for_status": lambda self: None, "json": lambda self: { "status": "completed", "output": [{"content": [{"type": "output_text", "text": json.dumps(design)}]}], }, }, )() def test_available_metrics_follow_dataset_mapping(workspace): ds, *_ = workspace assert set(available_metrics(ds)) == { "revenue", "cost", "gross", "roi", "customers", "basket", "margin", "discount_rate", "self_revenue", "upgrade_rate", "list_basket", } ds.mapping = { **ds.mapping, "columns": [ column for column in ds.mapping["columns"] if column["role"] not in {"cost", "gross", "customer", "list_amount"} ], "self_operated_products": [], "margin_rates": {}, } assert set(available_metrics(ds)) == {"revenue"} with pytest.raises(ValueError, match="消费人数"): ensure_available_metrics(ds, {"expression": {"metric": "customers"}}) def test_model_cannot_complete_definition_using_unavailable_metrics(workspace, settings): ds, *_ = workspace settings.AI_ENABLED = True settings.OPENAI_API_KEY = "test-key" settings.OPENAI_MODEL = "test-model" definition = { "name": "收入乘消费人数", "meaning": "实际收入乘消费人数", "scope": "按所选分组和期间", "missing": "结果不可用(#N/A);零分母不可用", "expression": { "operator": "multiply", "left": {"metric": "revenue"}, "right": {"metric": "customers"}, }, } ds.mapping = { **ds.mapping, "columns": [column for column in ds.mapping["columns"] if column["role"] != "customer"], } with patch("finance.formula_views.httpx.Client") as client: client.return_value.__enter__.return_value.post.return_value = model_response( {"complete": True, "questions": [], "column": definition} ) result = design_column( [{"role": "user", "content": "增加收入乘消费人数列"}], ds, {"current": ["2026-08"], "baseline": ["2025-08"], "filters": {}, "group": "department"}, ) assert result["complete"] is False assert result["column"] is None assert "消费人数" in result["questions"][0] payload = client.return_value.__enter__.return_value.post.call_args.kwargs["json"] system = payload["input"][0]["content"] assert '"customers": "消费人数"' in system.split('"unavailable_metrics":', 1)[1] metric_schema = payload["text"]["format"]["schema"]["properties"]["column"]["anyOf"][1]["properties"][ "expression" ]["anyOf"][0]["properties"]["metric"] assert "customers" not in metric_schema["enum"] assert "revenue" in metric_schema["enum"] def test_export_api_rejects_unavailable_metric_even_without_design_dialog(workspace): ds, _, client, *_ = workspace definition = { "name": "人数列", "meaning": "消费人数", "scope": "按分组期间", "missing": "结果不可用(#N/A);零分母不可用", "expression": {"metric": "customers"}, } ds.mapping = { **ds.mapping, "columns": [column for column in ds.mapping["columns"] if column["role"] != "customer"], } ds.save(update_fields=["mapping"]) response = client.post( f"/api/datasets/{ds.pk}/formula-exports", data={ "start": "2026-08", "end": "2026-08", "comparison": "yoy", "group": "department", "custom_columns": [definition], }, content_type="application/json", ) assert response.status_code == 400 assert "消费人数" in response.json()["error"]