# Copyright (c) 2025-2026, RTE (https://www.rte-france.com) # This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0. # If a copy of the Mozilla Public License, version 2.0 was not distributed with this file, # you can obtain one at http://mozilla.org/MPL/2.0/. # SPDX-License-Identifier: MPL-2.0 """Unit tests for the vectorised DiagramMixin._diff_switches (QW11). The old implementation looped over ~85 k switches doing two `.loc` lookups each; the vectorised version aligns the two `open` columns on the shared index and compares in NumPy. These tests lock the semantics the old loop had: only switches present in BOTH grids are considered, a change is reported as `from_open` (contingency) → `to_open` (action), and unchanged / absent switches are omitted. """ import pandas as pd from expert_backend.services.recommender_service import RecommenderService class _FakeNetwork: def __init__(self, switches_df): self._df = switches_df def get_switches(self): return self._df def _switches(open_by_id): return pd.DataFrame({"open": list(open_by_id.values())}, index=list(open_by_id)) def test_none_action_snapshot_returns_empty(): assert RecommenderService._diff_switches(None, _FakeNetwork(_switches({}))) == {} def test_detects_open_and_close_transitions(): action = _switches({"SW_A": True, "SW_B": False, "SW_C": True}) cont = _FakeNetwork(_switches({"SW_A": False, "SW_B": True, "SW_C": True})) result = RecommenderService._diff_switches(action, cont) # SW_A: closed→open, SW_B: open→closed, SW_C: unchanged (omitted). assert result == { "SW_A": {"from_open": False, "to_open": True}, "SW_B": {"from_open": True, "to_open": False}, } def test_switches_absent_from_contingency_are_skipped(): action = _switches({"SW_A": True, "SW_ONLY_IN_ACTION": True}) cont = _FakeNetwork(_switches({"SW_A": False})) result = RecommenderService._diff_switches(action, cont) assert result == {"SW_A": {"from_open": False, "to_open": True}} assert "SW_ONLY_IN_ACTION" not in result def test_no_changes_returns_empty(): action = _switches({"SW_A": True, "SW_B": False}) cont = _FakeNetwork(_switches({"SW_A": True, "SW_B": False})) assert RecommenderService._diff_switches(action, cont) == {} def test_failure_is_swallowed_to_empty(): class _Boom: def get_switches(self): raise RuntimeError("boom") # Switches are informational — a failure must not break the SLD response. assert RecommenderService._diff_switches(_switches({"SW_A": True}), _Boom()) == {}