# #region Test.DashboardTopology [C:3] [TYPE Module] [SEMANTICS test,topology,dashboard,superset] # @BRIEF Comprehensive tests for _topology.py — chart name/params, topology building, layout parsing. # @RELATION BINDS_TO -> [DashboardTopology] # @TEST_EDGE: chart_name_fallback -> Falls back to title or "Chart id" when slice_name missing # @TEST_EDGE: params_string_parse -> String params parsed, invalid string returns {} # @TEST_EDGE: describe_empty_optional -> No metrics/groupby/filters returns empty string # @TEST_EDGE: resolve_child_string -> String ref resolved from pos_json, missing ref returns None # @TEST_EDGE: topology_without_grid -> No GRID/ROOT nodes falls back to flat iteration # @TEST_EDGE: build_topology_string_posjson -> position_json as string is parsed import json from unittest.mock import MagicMock # ── _chart_name ── class TestChartName: """Verify _chart_name.""" def test_slice_name_used(self): from src.plugins.llm_analysis._topology import _chart_name assert _chart_name({"slice_name": "Revenue"}) == "Revenue" def test_title_fallback(self): from src.plugins.llm_analysis._topology import _chart_name assert _chart_name({"title": "Chart Title"}) == "Chart Title" def test_slice_name_overrides_title(self): from src.plugins.llm_analysis._topology import _chart_name assert _chart_name({"slice_name": "Actual", "title": "Fallback"}) == "Actual" def test_id_fallback(self): from src.plugins.llm_analysis._topology import _chart_name result = _chart_name({"id": 42}) assert result == "Chart 42" def test_empty_dict(self): from src.plugins.llm_analysis._topology import _chart_name result = _chart_name({}) assert result == "Chart None" # ── _chart_params ── class TestChartParams: """Verify _chart_params.""" def test_dict_params(self): from src.plugins.llm_analysis._topology import _chart_params result = _chart_params({"params": {"metrics": ["count"]}}) assert result == {"metrics": ["count"]} def test_string_params(self): from src.plugins.llm_analysis._topology import _chart_params result = _chart_params({"params": '{"metrics": ["count"]}'}) assert result == {"metrics": ["count"]} def test_invalid_string_params(self): from src.plugins.llm_analysis._topology import _chart_params result = _chart_params({"params": "not-json"}) assert result == {} def test_empty_params(self): from src.plugins.llm_analysis._topology import _chart_params assert _chart_params({}) == {} def test_none_params(self): from src.plugins.llm_analysis._topology import _chart_params # When params is None, returns None (params value as-is) assert _chart_params({"params": None}) is None # ── _describe_chart_dataset ── class TestDescribeChartDataset: """Verify _describe_chart_dataset.""" def test_with_datasource_id(self): from src.plugins.llm_analysis._topology import _describe_chart_dataset result = _describe_chart_dataset({"datasource_id": 5}, {}) assert "Dataset: 5" in result def test_datasource_id_in_params(self): from src.plugins.llm_analysis._topology import _describe_chart_dataset result = _describe_chart_dataset({}, {"datasource_id": 10}) assert "Dataset: 10" in result def test_no_datasource(self): from src.plugins.llm_analysis._topology import _describe_chart_dataset assert _describe_chart_dataset({}, {}) == "" # ── _describe_chart_metrics ── class TestDescribeChartMetrics: """Verify _describe_chart_metrics.""" def test_dict_metrics(self): from src.plugins.llm_analysis._topology import _describe_chart_metrics result = _describe_chart_metrics({"metrics": [{"label": "SUM(revenue)"}]}) assert "SUM(revenue)" in result def test_string_metrics(self): from src.plugins.llm_analysis._topology import _describe_chart_metrics result = _describe_chart_metrics({"metrics": ["count"]}) assert "count" in result def test_dict_metric_expression_fallback(self): from src.plugins.llm_analysis._topology import _describe_chart_metrics result = _describe_chart_metrics({"metrics": [{"expression": "SUM(x)"}]}) assert "SUM(x)" in result def test_empty_metrics(self): from src.plugins.llm_analysis._topology import _describe_chart_metrics assert _describe_chart_metrics({"metrics": []}) == "" def test_missing_metrics(self): from src.plugins.llm_analysis._topology import _describe_chart_metrics assert _describe_chart_metrics({}) == "" def test_truncated_to_three(self): from src.plugins.llm_analysis._topology import _describe_chart_metrics metrics = [str(i) for i in range(10)] result = _describe_chart_metrics({"metrics": metrics}) assert len(result.split(",")) <= 4 # "Metrics: " prefix + 3 items max # ── _describe_chart_groupby ── class TestDescribeChartGroupby: """Verify _describe_chart_groupby.""" def test_with_groupby(self): from src.plugins.llm_analysis._topology import _describe_chart_groupby result = _describe_chart_groupby({"groupby": ["category", "region"]}) assert "category" in result assert "region" in result def test_truncated_to_three(self): from src.plugins.llm_analysis._topology import _describe_chart_groupby result = _describe_chart_groupby({"groupby": ["a", "b", "c", "d"]}) assert "d" not in result def test_empty_groupby(self): from src.plugins.llm_analysis._topology import _describe_chart_groupby assert _describe_chart_groupby({"groupby": []}) == "" def test_missing_groupby(self): from src.plugins.llm_analysis._topology import _describe_chart_groupby assert _describe_chart_groupby({}) == "" # ── _describe_chart_filters ── class TestDescribeChartFilters: """Verify _describe_chart_filters.""" def test_full_filter(self): from src.plugins.llm_analysis._topology import _describe_chart_filters result = _describe_chart_filters({ "adhoc_filters": [ {"subject": "revenue", "operator": ">", "comparator": "1000"} ] }) assert "revenue" in result assert ">" in result def test_filter_without_subject_comparator(self): from src.plugins.llm_analysis._topology import _describe_chart_filters result = _describe_chart_filters({ "adhoc_filters": [ {"clause": "having"} ] }) assert "having" in result def test_empty_filters(self): from src.plugins.llm_analysis._topology import _describe_chart_filters assert _describe_chart_filters({"adhoc_filters": []}) == "" def test_missing_filters(self): from src.plugins.llm_analysis._topology import _describe_chart_filters assert _describe_chart_filters({}) == "" def test_non_dict_filter(self): from src.plugins.llm_analysis._topology import _describe_chart_filters result = _describe_chart_filters({"adhoc_filters": ["raw_string"]}) assert result == "" # not a dict, no clause key # ── _describe_chart ── class TestDescribeChart: """Verify _describe_chart orchestration.""" def test_describe_integration(self): from src.plugins.llm_analysis._topology import _describe_chart chart = { "slice_name": "Sales", "viz_type": "bar", "params": {"metrics": [{"label": "SUM(rev)"}]}, } result = _describe_chart(chart) assert 'Chart "Sales" (bar)' in result assert "SUM(rev)" in result def test_describe_minimal(self): from src.plugins.llm_analysis._topology import _describe_chart result = _describe_chart({"id": 99}) assert 'Chart "Chart 99" (unknown)' in result # ── _sort_chart_key ── class TestSortChartKey: """Verify _sort_chart_key.""" def test_numeric_key(self): from src.plugins.llm_analysis._topology import _sort_chart_key result = _sort_chart_key(("42", {})) assert result == (0, 42) def test_string_key(self): from src.plugins.llm_analysis._topology import _sort_chart_key result = _sort_chart_key(("chart_1", {})) assert result == (1, "chart_1") def test_non_numeric_key(self): from src.plugins.llm_analysis._topology import _sort_chart_key result = _sort_chart_key(("abc", {})) assert result == (1, "abc") # ── _resolve_child ── class TestResolveChild: """Verify _resolve_child.""" def test_dict_ref_returns_as_is(self): from src.plugins.llm_analysis._topology import _resolve_child ref = {"type": "CHART"} assert _resolve_child(ref, {}) is ref def test_string_ref_found(self): from src.plugins.llm_analysis._topology import _resolve_child pos_json = {"key1": {"type": "CHART"}} result = _resolve_child("key1", pos_json) assert result == {"type": "CHART"} def test_string_ref_not_found(self): from src.plugins.llm_analysis._topology import _resolve_child assert _resolve_child("missing", {}) is None # ── _process_tab ── class TestProcessTab: """Verify _process_tab.""" def test_tab_with_children(self): from src.plugins.llm_analysis._topology import _process_tab tab_node = { "type": "TAB", "meta": {"text": "Revenue Tab"}, "children": [ {"type": "CHART", "meta": {"chartId": 1}}, ] } result = _process_tab(tab_node, depth=1, pos_json={}, chart_lookup={"1": {"slice_name": "Rev Chart"}}) assert len(result) == 2 assert "Revenue Tab" in result[0] assert "Rev Chart" in result[1] def test_tab_meta_label_fallback(self): from src.plugins.llm_analysis._topology import _process_tab tab_node = {"type": "TAB", "meta": {"label": "Tab Label"}, "children": []} result = _process_tab(tab_node, depth=0, pos_json={}, chart_lookup={}) assert "Tab Label" in result[0] def test_tab_empty_meta(self): from src.plugins.llm_analysis._topology import _process_tab tab_node = {"children": []} result = _process_tab(tab_node, depth=0, pos_json={}, chart_lookup={}) assert "Tab" in result[0] # ── _process_chart ── class TestProcessChart: """Verify _process_chart.""" def test_chart_found_in_lookup(self): from src.plugins.llm_analysis._topology import _process_chart child = {"meta": {"chartId": 5}} chart_lookup = {"5": {"slice_name": "Test Chart", "viz_type": "line"}} result = _process_chart(child, depth=0, chart_lookup=chart_lookup) assert len(result) == 1 assert "Test Chart" in result[0] def test_chart_not_in_lookup(self): from src.plugins.llm_analysis._topology import _process_chart child = {"meta": {"chartId": 99}} result = _process_chart(child, depth=0, chart_lookup={}) assert result == [] def test_chart_with_slice_id(self): from src.plugins.llm_analysis._topology import _process_chart child = {"meta": {"slice_id": 10}} chart_lookup = {"10": {"slice_name": "Slice Chart"}} result = _process_chart(child, depth=0, chart_lookup=chart_lookup) assert len(result) == 1 def test_chart_missing_meta(self): from src.plugins.llm_analysis._topology import _process_chart result = _process_chart({}, depth=0, chart_lookup={"1": {}}) assert result == [] # ── _process_children ── class TestProcessChildren: """Verify _process_children.""" def test_tab_child(self): from src.plugins.llm_analysis._topology import _process_children parent = { "children": [ {"type": "TAB", "meta": {"text": "Tab1"}, "children": []} ] } result = _process_children(parent, depth=0, pos_json={}, chart_lookup={}) assert len(result) == 1 assert "Tab1" in result[0] def test_row_child(self): from src.plugins.llm_analysis._topology import _process_children parent = { "children": [ {"type": "ROW", "children": [ {"type": "CHART", "meta": {"chartId": 1}} ]} ] } chart_lookup = {"1": {"slice_name": "C1"}} result = _process_children(parent, depth=0, pos_json={}, chart_lookup=chart_lookup) assert len(result) == 1 def test_unknown_type_skipped(self): from src.plugins.llm_analysis._topology import _process_children parent = {"children": [{"type": "HEADER", "meta": {}}]} result = _process_children(parent, depth=0, pos_json={}, chart_lookup={}) assert result == [] def test_child_ref_resolved(self): from src.plugins.llm_analysis._topology import _process_children parent = {"children": ["ref_key"]} pos_json = {"ref_key": {"type": "CHART", "meta": {"chartId": 1}}} chart_lookup = {"1": {"slice_name": "Ref C"}} result = _process_children(parent, depth=0, pos_json=pos_json, chart_lookup=chart_lookup) assert len(result) == 1 def test_child_ref_not_resolved(self): from src.plugins.llm_analysis._topology import _process_children parent = {"children": ["missing_key"]} result = _process_children(parent, depth=0, pos_json={}, chart_lookup={}) assert result == [] def test_non_dict_parent(self): from src.plugins.llm_analysis._topology import _process_children result = _process_children("not_a_dict", depth=0, pos_json={}, chart_lookup={}) assert result == [] # ── _extract_topology_children ── class TestExtractTopologyChildren: """Verify _extract_topology_children.""" def test_grid_nodes(self): from src.plugins.llm_analysis._topology import _extract_topology_children pos_json = { "GRID_ID": {"type": "GRID", "children": [ {"type": "CHART", "meta": {"chartId": 1}} ]} } chart_lookup = {"1": {"slice_name": "GChart"}} result = _extract_topology_children(pos_json, chart_lookup) assert len(result) >= 1 def test_no_grid_fallback(self): from src.plugins.llm_analysis._topology import _extract_topology_children pos_json = { "tab1": {"type": "TAB", "children": []}, "DASHBOARD_VERSION_KEY": "v2", } result = _extract_topology_children(pos_json, chart_lookup={}) assert isinstance(result, list) def test_no_grid_skips_version_key(self): from src.plugins.llm_analysis._topology import _extract_topology_children pos_json = {"DASHBOARD_VERSION_KEY": "v2"} result = _extract_topology_children(pos_json, chart_lookup={}) assert result == [] def test_empty_position_json(self): from src.plugins.llm_analysis._topology import _extract_topology_children assert _extract_topology_children({}, {}) == [] # ── build_dashboard_topology (integration) ── class TestBuildDashboardTopology: """Verify build_dashboard_topology full function.""" def test_happy_path(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = { "id": 1, "dashboard_title": "Sales Overview", "position_json": { "ROOT_ID": { "type": "ROOT", "children": [ {"type": "CHART", "meta": {"chartId": 10}} ] } }, } charts = [{"id": 10, "slice_name": "Revenue", "viz_type": "big_number"}] result = build_dashboard_topology(dashboard, charts) assert 'Dashboard: "Sales Overview"' in result assert "Revenue" in result assert "Charts: 1 total" in result def test_title_fallback(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = {"title": "Alt Title", "position_json": {}} result = build_dashboard_topology(dashboard, []) assert "Alt Title" in result def test_no_title_fallback(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = {"id": 5, "position_json": {}} result = build_dashboard_topology(dashboard, []) assert "Dashboard 5" in result def test_position_json_as_string(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = { "dashboard_title": "Test", "position_json": json.dumps({ "ROOT_ID": {"type": "ROOT", "children": []} }), } result = build_dashboard_topology(dashboard, []) assert "Test" in result def test_position_json_invalid_string(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = { "dashboard_title": "Broken", "position_json": "not-valid-json{{", } result = build_dashboard_topology(dashboard, []) assert "Broken" in result # gracefully handles parse failure def test_position_json_not_dict(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = { "dashboard_title": "NoPos", "position_json": [], } result = build_dashboard_topology(dashboard, []) assert "NoPos" in result def test_chart_lookup_with_slice_id(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = { "dashboard_title": "Test", "position_json": {}, } charts = [{"slice_id": 1, "slice_name": "Slice Chart", "viz_type": "table"}] result = build_dashboard_topology(dashboard, charts) assert "Slice Chart" in result def test_topology_parse_warning(self): """Exception during _extract_topology_children is caught and noted.""" from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = { "dashboard_title": "Err", "position_json": { "ROOT_ID": {"type": "ROOT", "children": ["missing"]} }, } # missing key in position_json triggers no error since _resolve_child returns None result = build_dashboard_topology(dashboard, []) assert "Err" in result def test_charts_sorted_by_key(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = { "dashboard_title": "Sorted", "position_json": {}, } charts = [ {"id": 10, "slice_name": "Z Chart"}, {"id": 5, "slice_name": "A Chart"}, ] result = build_dashboard_topology(dashboard, charts) # Both appear assert "A Chart" in result assert "Z Chart" in result def test_empty_charts(self): from src.plugins.llm_analysis._topology import build_dashboard_topology dashboard = {"dashboard_title": "Empty", "position_json": {}} result = build_dashboard_topology(dashboard, []) assert "Empty" in result assert "Charts: 0 total" not in result # no chart_lookup when charts empty # #endregion Test.DashboardTopology