import dash_ag_grid as dag import src.app # noqa: F401 # instancie l'app → schema chargé from src.db import query_marches from src.utils.entity_grid import ( build_entity_grid, clear_sort, entity_grid_column_defs, entity_scope, export_entity_dataframe, fetch_entity_page, grid_type, ) def _an_acheteur_id(): return query_marches("TRUE", (), columns=["acheteur_id"])["acheteur_id"][0] def test_entity_scope_acheteur(): where, params = entity_scope("acheteur", "S123", None) assert where == "acheteur_id = ?" assert params == ["S123"] def test_entity_scope_titulaire_requires_siret(): where, params = entity_scope("titulaire", "S123", None) assert "titulaire_id = ?" in where assert "titulaire_typeIdentifiant = 'SIRET'" in where assert params == ["S123"] def test_entity_scope_with_year_adds_bound_param(): where, params = entity_scope("acheteur", "S123", "2025") assert 'YEAR("dateNotification") = ?' in where assert params == ["S123", 2025] def test_entity_scope_toutes_les_annees_no_year_filter(): where, params = entity_scope("acheteur", "S123", "Toutes les années") assert "YEAR" not in where assert params == ["S123"] def test_fetch_entity_page_is_scoped(): ach = _an_acheteur_id() request = {"startRow": 0, "endRow": 50, "filterModel": None, "sortModel": None} response, total, total_unique = fetch_entity_page("acheteur", ach, None, request) assert total > 0 assert total_unique > 0 assert response["rowCount"] == total assert len(response["rowData"]) <= 50 # Toutes les lignes appartiennent à l'acheteur scopé. assert all(str(r["acheteur_id"]).find(str(ach)) != -1 for r in response["rowData"]) def test_fetch_entity_page_none_request(): from dash import no_update out = fetch_entity_page("acheteur", "S123", None, None) assert out == (no_update, no_update, no_update) def test_export_entity_dataframe_scoped(): ach = _an_acheteur_id() df = export_entity_dataframe("acheteur", ach, None, None, None) assert df.height > 0 assert set(df["acheteur_id"].to_list()) == {ach} def test_clear_sort_preserves_width_and_order(): column_state = [ {"colId": "objet", "width": 400, "sort": "asc", "sortIndex": 0}, {"colId": "montant", "width": 150, "sort": "desc", "sortIndex": 1}, ] cleared = clear_sort(column_state) assert all(c["sort"] is None and c["sortIndex"] is None for c in cleared) assert cleared[0]["width"] == 400 and cleared[0]["colId"] == "objet" assert cleared[1]["width"] == 150 def test_clear_sort_empty(): assert clear_sort(None) == [] def test_entity_grid_column_defs_applies_persisted_layout(): defs = entity_grid_column_defs( hidden_columns=[], column_state=[{"colId": "montant", "width": 999}] ) by_field = {d["field"]: d for d in defs} assert by_field["montant"]["width"] == 999 def test_build_entity_grid_has_pattern_matching_id_and_filter_only_persistence(): grid = build_entity_grid("acheteur", "S123", "Toutes les années", [], None) assert isinstance(grid, dag.AgGrid) assert grid.id == { "type": "acheteur-grid", "entity_id": "S123", "year": "Toutes les années", } assert list(grid.persisted_props) == ["filterModel"] def test_grid_type(): assert grid_type("acheteur") == "acheteur-grid" assert grid_type("titulaire") == "titulaire-grid" def test_register_entity_grid_callbacks_registered_via_page_import(): """Les callbacks de grille entité sont enregistrés (une fois) à l'import des pages acheteur/titulaire — pas de double enregistrement.""" import dash from src.app import app # importe les pages → register_entity_grid_callbacks # Tant que l'app n'a pas été servie, les callbacks enregistrés via le # décorateur global `dash.callback` vivent dans # dash._callback.GLOBAL_CALLBACK_MAP ; ils ne migrent vers # app.callback_map qu'à la première requête (_setup_server). On inspecte # donc les deux registres pour ne pas dépendre de l'ordre des tests # (voir tests/test_csrf_architecture.py pour le même motif). callback_keys = set(dash._callback.GLOBAL_CALLBACK_MAP) | set(app.callback_map) # Le store columnState partagé est piloté par un callback enregistré. assert any("entity-grid-columns-state.data" in k for k in callback_keys)