"""Tests for GUI profile persistence.""" from __future__ import annotations import unittest from python_app.models.gui_profile_schema import ( GuiPassThroughStateModel, GuiProcessingStateModel, GuiProfileModel, GuiSar3dStateModel, GuiStateModel, ) class GuiProfileCodecTest(unittest.TestCase): def test_pass_through_combo_filter_round_trips(self) -> None: profile = GuiProfileModel( gui=GuiStateModel( processing=GuiProcessingStateModel( pass_through=GuiPassThroughStateModel(combo_filter="0:0,1:0") ) ) ) encoded = profile.to_dict() decoded = GuiProfileModel.from_dict(encoded) self.assertIsNotNone(decoded.gui) assert decoded.gui is not None self.assertEqual(decoded.gui.processing.pass_through.combo_filter, "0:0,1:0") self.assertEqual(encoded["gui"]["processing"]["pass_through"]["combo_filter"], "0:0,1:0") def test_pass_through_unwrap_phase_round_trips(self) -> None: profile = GuiProfileModel( gui=GuiStateModel( processing=GuiProcessingStateModel( pass_through=GuiPassThroughStateModel(unwrap_phase=True) ) ) ) encoded = profile.to_dict() decoded = GuiProfileModel.from_dict(encoded) assert decoded.gui is not None self.assertTrue(decoded.gui.processing.pass_through.unwrap_phase) self.assertTrue(encoded["gui"]["processing"]["pass_through"]["unwrap_phase"]) def test_default_profile_round_trips_idempotently(self) -> None: # Full-subtree idempotence catches field-drop/mis-map regressions across every # sub-model, which the single combo_filter round-trip above cannot. once = GuiProfileModel().to_dict() twice = GuiProfileModel.from_dict(once).to_dict() self.assertEqual(once["gui"], twice["gui"]) def test_legacy_save_count_is_ignored_without_losing_save_destination(self) -> None: decoded = GuiProfileModel.from_dict({ "gui": {"data_actions": { "save_count": 7, "save_path": "/tmp/measurements", "save_name": "scene", "record_count": 250, }}, }) encoded = decoded.to_dict()["gui"]["data_actions"] self.assertNotIn("save_count", encoded) self.assertEqual(encoded, { "save_path": "/tmp/measurements", "save_name": "scene", "record_count": 250, }) def test_sar3d_section_round_trips(self) -> None: sar3d = GuiSar3dStateModel( input_positions="0,2", output_positions="1,3", min_depth_m=2.5, max_depth_m=9.0, start_freq_mhz=2000.0, stop_freq_mhz=5500.0, speed_m_s=1.42, ignore_socket_speed_enabled=True, look_angle_deg=6.5, frame_radius=3, voxel_x_m=0.035, voxel_y_m=0.03, voxel_z_m=0.025, bp_ground_depth_m=1.1, comp_range_power=0.5, use_compensation=False, normalize_each_frame=False, ) profile = GuiProfileModel(gui=GuiStateModel(processing=GuiProcessingStateModel(sar3d=sar3d))) decoded = GuiProfileModel.from_dict(profile.to_dict()) assert decoded.gui is not None self.assertEqual(decoded.gui.processing.sar3d, sar3d) def test_missing_gui_section_yields_none(self) -> None: self.assertIsNone(GuiProfileModel.from_dict({}).gui) def test_unsupported_version_is_rejected(self) -> None: with self.assertRaisesRegex(ValueError, "version"): GuiProfileModel.from_dict({"gui": {"version": 2}}) def test_invalid_combo_mode_is_rejected(self) -> None: with self.assertRaisesRegex(ValueError, "combo_mode"): GuiProfileModel.from_dict({"gui": {"switches": {"combo_mode": "bogus"}}}) def test_wrong_typed_field_is_rejected(self) -> None: with self.assertRaises(ValueError): # combos_text must be a JSON string, not a number GuiProfileModel.from_dict({"gui": {"switches": {"combos_text": 123}}}) def test_obsolete_inactive_settings_are_ignored(self) -> None: decoded = GuiProfileModel.from_dict({ "gui": {"processing": {"selected_mode": "gpr", "legacy_gpr": "obsolete", "gpr": {"algorithm": "legacy_point"}}}, "gpr": {"relative_permittivity": "obsolete", "mode": "point"}, }) assert decoded.gui is not None self.assertEqual(decoded.gui.processing.selected_mode, "gpr") self.assertNotIn("legacy_gpr", decoded.to_dict()["gui"]["processing"]) self.assertNotIn("relative_permittivity", decoded.to_dict()["gpr"]) def test_removed_processing_mode_is_rejected(self) -> None: with self.assertRaisesRegex(ValueError, "selected_mode must be one of"): GuiProfileModel.from_dict({"gui": {"processing": {"selected_mode": "legacy_gpr"}}}) def test_modern_gpr_selection_is_not_migrated(self) -> None: decoded = GuiProfileModel.from_dict({"gui": {"processing": {"selected_mode": "gpr"}}}) assert decoded.gui is not None self.assertEqual(decoded.gui.processing.selected_mode, "gpr") if __name__ == "__main__": unittest.main()