"""Workbook dashboard: named ranges, chart/pivot objects and per-sheet KPI stats (#153 A17), plus the ``GET …/xlsx/dashboard`` endpoint wiring.""" from __future__ import annotations from pathlib import Path import pytest openpyxl = pytest.importorskip("openpyxl") VAULT = "TestVault" @pytest.fixture def dash_book(test_vault_dir: str) -> str: """A workbook with a named range, a chart and numeric KPI cells.""" from openpyxl import Workbook from openpyxl.chart import BarChart, Reference from openpyxl.workbook.defined_name import DefinedName path = Path(test_vault_dir) / "dash.xlsx" wb = Workbook() ws = wb.active ws.title = "Data" ws["A1"] = "Ventes" ws["A2"] = 12 ws["A3"] = 48 ws["B2"] = "=SUM(A2:A3)" wb.defined_names.add(DefinedName("MaPlage", attr_text="Data!$A$1:$B$5")) chart = BarChart() chart.add_data(Reference(ws, min_col=1, min_row=1, max_row=3)) ws.add_chart(chart, "D2") wb.save(path) return str(path) @pytest.fixture def plain_book(test_vault_dir: str) -> str: """A workbook without any dashboard-worthy feature.""" from openpyxl import Workbook path = Path(test_vault_dir) / "plain.xlsx" wb = Workbook() ws = wb.active ws.title = "Vide" ws["A1"] = "texte seul" wb.save(path) return str(path) class TestDashboardReading: def test_named_range_is_detected(self, dash_book): from backend.xlsx_reader import read_workbook_dashboard dash = read_workbook_dashboard(Path(dash_book)) names = [r["name"] for r in dash["named_ranges"]] assert "MaPlage" in names entry = next(r for r in dash["named_ranges"] if r["name"] == "MaPlage") assert "Data!" in entry["ref"] def test_charts_are_counted(self, dash_book): from backend.xlsx_reader import read_workbook_dashboard dash = read_workbook_dashboard(Path(dash_book)) assert dash["objects"]["charts"] >= 1 assert dash["objects"]["pivots"] == 0 def test_sheet_kpis_carry_numeric_cells(self, dash_book): from backend.xlsx_reader import read_workbook_dashboard dash = read_workbook_dashboard(Path(dash_book)) sheet = next(s for s in dash["sheets"] if s["name"] == "Data") assert sheet["cells"] >= 4 assert sheet["formulas"] == 1 assert sheet["numeric"] >= 2 values = [k["value"] for k in sheet["kpi"]] assert 12 in values and 48 in values def test_plain_book_yields_empty_dashboard(self, plain_book): from backend.xlsx_reader import read_workbook_dashboard dash = read_workbook_dashboard(Path(plain_book)) assert dash["named_ranges"] == [] assert dash["objects"]["charts"] == 0 sheet = dash["sheets"][0] assert sheet["numeric"] == 0 assert sheet["kpi"] == [] def test_broken_book_yields_empty_payload(self, test_vault_dir): from backend.xlsx_reader import read_workbook_dashboard bad = Path(test_vault_dir) / "broken-dash.xlsx" bad.write_bytes(b"not a zip") dash = read_workbook_dashboard(bad) assert dash["named_ranges"] == [] assert dash["sheets"] == [] class TestDashboardEndpoint: def test_endpoint_serves_the_dashboard(self, client, dash_book): resp = client.get(f"/api/file/{VAULT}", params={"path": dash_book}) assert resp.status_code == 200 # sanity: file is readable dash = client.get( f"/api/file/{VAULT}/xlsx/dashboard", params={"path": dash_book} ).json() assert any(r["name"] == "MaPlage" for r in dash["named_ranges"]) assert dash["objects"]["charts"] >= 1 assert any(s["kpi"] for s in dash["sheets"]) def test_unknown_file_is_404(self, client): resp = client.get( f"/api/file/{VAULT}/xlsx/dashboard", params={"path": "nope.xlsx"} ) assert resp.status_code == 404 def test_non_workbook_is_415(self, client, test_vault_dir): (Path(test_vault_dir) / "texte.txt").write_text("hello", encoding="utf-8") resp = client.get( f"/api/file/{VAULT}/xlsx/dashboard", params={"path": "texte.txt"} ) assert resp.status_code == 415