"""Settings commits retain valid files on failure and concurrent writes.""" from concurrent.futures import ThreadPoolExecutor import json from pathlib import Path import tempfile import unittest from unittest import mock from python_app.file_io import atomic_write_json from python_app.orchestration.gui_session_state import GuiSessionStateStore class AtomicJsonTest(unittest.TestCase): def setUp(self) -> None: directory = tempfile.TemporaryDirectory() self.addCleanup(directory.cleanup) self.path = Path(directory.name) / "settings.json" atomic_write_json(self.path, {"old": True}) def test_failed_sync_or_commit_preserves_previous_file(self) -> None: for operation in ('fsync', 'replace'): with self.subTest(operation=operation), \ mock.patch(f'python_app.file_io.os.{operation}', side_effect=OSError('disk failure')), \ self.assertRaises(OSError): atomic_write_json(self.path, {'new': True}, durable=True) self.assertEqual(json.loads(self.path.read_text()), {'old': True}) self.assertEqual(list(self.path.parent.glob('*.tmp')), []) def test_nonfinite_json_cannot_overwrite_settings(self) -> None: with self.assertRaises(ValueError): atomic_write_json(self.path, {'bad': float('nan')}) self.assertEqual(json.loads(self.path.read_text()), {'old': True}) def test_concurrent_writers_publish_complete_json_and_leave_no_temp_files(self) -> None: payloads = [{'index': i, 'values': [i] * 1000} for i in range(32)] with ThreadPoolExecutor(max_workers=8) as executor: list(executor.map(lambda payload: atomic_write_json(self.path, payload), payloads)) self.assertIn(json.loads(self.path.read_text()), payloads) self.assertEqual(list(self.path.parent.glob('*.tmp')), []) def test_invalid_utf8_session_cache_uses_defaults(self) -> None: self.path.write_bytes(b'\xff') self.assertEqual(GuiSessionStateStore(self.path).load().last_profile_path, '')