zapret-kvn/tests/test_state_storage_key_cache.py
loop-uh bd79c212e6
Some checks failed
Windows project source guards / test (push) Failing after 36s
Сохранение состояния и истории трафика без блокировки GUI
- controller.save(): в GUI-потоке только снимок в текст (~5 мс на 1000
  серверов вместо deepcopy 22 мс); шифрование и запись — единый фоновый
  StateWriter, промежуточные снимки схлопываются в последний. На выходе —
  save_blocking() с ожиданием записи.
- PBKDF2 (480k итераций) считается один раз за сессию на пароль, а не на
  каждое сохранение; формат файла прежний, Fernet берёт случайный IV сам.
- История трафика: закрытые сессии кодируются в JSON один раз, запись через
  фоновый писатель, компактный JSON вместо indent=2.
- Перетаскивание/ресайз окна больше не планируют сохранение состояния.
2026-09-26 12:30:40 +03:00

67 lines
2.6 KiB
Python

"""Сохранение состояния: PBKDF2 один раз за сессию, формат файла прежний."""
from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest import mock
from xray_fluent.platform.windows import security
from xray_fluent.profiles import storage as storage_module
from xray_fluent.profiles.models import AppState, Node
from xray_fluent.profiles.storage import StateStorage
class StateStorageKeyCacheTests(unittest.TestCase):
def setUp(self) -> None:
self._tmp = tempfile.TemporaryDirectory()
self.addCleanup(self._tmp.cleanup)
self.state_file = Path(self._tmp.name) / "state.json"
patcher = mock.patch.object(StateStorage, "_ensure_dirs", lambda self: None)
patcher.start()
self.addCleanup(patcher.stop)
def _state(self, name: str) -> AppState:
state = AppState()
state.nodes = [Node(id="a", name=name)]
return state
def test_repeated_saves_derive_key_once(self) -> None:
storage = StateStorage(self.state_file)
storage.passphrase = "secret"
with mock.patch.object(
storage_module, "derive_passphrase_key", wraps=security.derive_passphrase_key
) as derive:
for index in range(3):
storage.save(self._state(f"n{index}"))
self.assertEqual(derive.call_count, 1)
def test_saved_file_decrypts_with_the_standard_routine(self) -> None:
storage = StateStorage(self.state_file)
storage.passphrase = "secret"
storage.save(self._state("first"))
storage.save(self._state("second"))
text = self.state_file.read_text(encoding="utf-8")
self.assertTrue(security.is_passphrase_encrypted(text))
self.assertIn(b'"second"', security.decrypt_with_passphrase(text, "secret"))
def test_passphrase_change_derives_new_key(self) -> None:
storage = StateStorage(self.state_file)
storage.passphrase = "one"
storage.save(self._state("x"))
storage.passphrase = "two"
storage.save(self._state("y"))
text = self.state_file.read_text(encoding="utf-8")
with self.assertRaises(ValueError):
security.decrypt_with_passphrase(text, "one")
self.assertIn(b'"y"', security.decrypt_with_passphrase(text, "two"))
def test_plain_save_without_passphrase(self) -> None:
storage = StateStorage(self.state_file)
storage.save(self._state("plain"))
self.assertTrue(self.state_file.read_text(encoding="utf-8").lstrip().startswith("{"))
if __name__ == "__main__":
unittest.main()