zapretgui/tests/test_premium_direct_network.py
loop-uh 3bd2973c7e
All checks were successful
Zapret source guards / validate (push) Successful in 9s
Premium: подписка больше не «пропадает» после перезапуска и обновления — статус сам сверяется с сервером при запуске и дальше по расписанию, а привязка по коду подхватывается без кнопки «Обновить статус»
Причина: в сеть за статусом ходила только страница Premium (по кнопке и
пока открыта). Запуск читал один сохранённый подписанный ответ сервера,
а он живёт 7 дней — после этого любая перезагрузка показывала Free.
Опрос после создания кода был выключен, если на устройстве уже лежал
старый токен, и останавливался при уходе со страницы.

Теперь проверкой владеет один объект на всё время работы программы
(donater/status_runtime.py): сохранённый статус при запуске, затем сервер,
дальше повтор по правилам donater/status_schedule.py; свежий код
опрашивается каждые 3 с независимо от страницы и старого токена.
Страница только просит обновление и рисует итог.

Запрос к серверу Premium идёт обычным путём, а winws2 приостанавливается
только когда он мешает соединению — иначе автоматические проверки
дёргали бы обход.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 15:01:24 +03:00

289 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import socket
import threading
import unittest
from unittest.mock import Mock, patch
import requests
class PremiumDirectNetworkTests(unittest.TestCase):
@staticmethod
def _ok_response() -> Mock:
response = Mock()
response.content = b'{"success": true}'
response.status_code = 200
response.json.return_value = {"success": True}
return response
def test_service_has_no_own_schedule(self) -> None:
# Когда идти в сеть, решает donater.status_runtime; сервис выполняет
# ровно ту проверку, о которой его попросили.
import inspect
from donater.service import PremiumService
self.assertFalse(hasattr(PremiumService, "_automatic_network_due"))
self.assertNotIn("automatic", inspect.signature(PremiumService.check_status).parameters)
self.assertNotIn(
"automatic",
inspect.signature(PremiumService.check_device_activation).parameters,
)
def test_request_does_not_pause_winws2_when_plain_route_works(self) -> None:
from donater.api import PremiumApiClient
client = PremiumApiClient(base_url="https://premium.example/api")
with patch(
"donater.api.request_with_dns_fallback",
return_value=self._ok_response(),
), patch(
"winws_runtime.runtime.direct_network.run_with_direct_network_access",
) as direct, patch(
"winws_runtime.runtime.direct_network.direct_network_pause_needed",
return_value=True,
):
result = client.get_status()
self.assertTrue(result["success"])
direct.assert_not_called()
self.assertFalse(client.uses_direct_window)
def test_plain_route_failure_falls_back_to_direct_window_and_remembers_it(self) -> None:
from donater.api import PremiumApiClient
client = PremiumApiClient(base_url="https://premium.example/api")
sends = Mock(
side_effect=(
requests.exceptions.SSLError("desync broke tls"),
self._ok_response(),
self._ok_response(),
)
)
with patch("donater.api.request_with_dns_fallback", sends), patch(
"winws_runtime.runtime.direct_network.run_with_direct_network_access",
side_effect=lambda operation: operation(),
) as direct, patch(
"winws_runtime.runtime.direct_network.direct_network_pause_needed",
return_value=True,
):
first = client.get_status()
self.assertTrue(first["success"])
self.assertEqual(direct.call_count, 1)
self.assertTrue(client.uses_direct_window)
# Следующий запрос не тратит время на заведомо неудачную попытку.
second = client.get_status()
self.assertTrue(second["success"])
self.assertEqual(direct.call_count, 2)
self.assertEqual(sends.call_count, 3)
def test_plain_route_failure_without_running_winws2_is_not_retried(self) -> None:
from donater.api import PremiumApiClient
client = PremiumApiClient(base_url="https://premium.example/api")
sends = Mock(side_effect=requests.exceptions.ConnectTimeout("offline"))
with patch("donater.api.request_with_dns_fallback", sends), patch(
"winws_runtime.runtime.direct_network.run_with_direct_network_access",
) as direct, patch(
"winws_runtime.runtime.direct_network.direct_network_pause_needed",
return_value=False,
):
result = client.get_status()
self.assertEqual(result["error"]["code"], "connect_timeout")
direct.assert_not_called()
sends.assert_called_once()
def test_useless_direct_window_is_not_repeated_while_server_is_down(self) -> None:
from donater.api import PremiumApiClient
client = PremiumApiClient(base_url="https://premium.example/api")
sends = Mock(side_effect=requests.exceptions.ConnectTimeout("server down"))
with patch("donater.api.request_with_dns_fallback", sends), patch(
"winws_runtime.runtime.direct_network.run_with_direct_network_access",
side_effect=lambda operation: operation(),
) as direct, patch(
"winws_runtime.runtime.direct_network.direct_network_pause_needed",
return_value=True,
):
for _attempt in range(4):
result = client.get_status()
self.assertEqual(result["error"]["code"], "connect_timeout")
# Пауза winws2 не помогла — повторные паузы только рвали бы обход.
self.assertEqual(direct.call_count, 1)
self.assertFalse(client.uses_direct_window)
def test_direct_window_memory_is_dropped_when_direct_route_fails(self) -> None:
from donater.api import PremiumApiClient
client = PremiumApiClient(base_url="https://premium.example/api")
sends = Mock(
side_effect=(
requests.exceptions.SSLError("desync broke tls"),
self._ok_response(),
requests.exceptions.ConnectTimeout("server down"),
requests.exceptions.ConnectTimeout("server down"),
)
)
with patch("donater.api.request_with_dns_fallback", sends), patch(
"winws_runtime.runtime.direct_network.run_with_direct_network_access",
side_effect=lambda operation: operation(),
) as direct, patch(
"winws_runtime.runtime.direct_network.direct_network_pause_needed",
return_value=True,
):
self.assertTrue(client.get_status()["success"])
self.assertTrue(client.uses_direct_window)
self.assertEqual(client.get_status()["error"]["code"], "connect_timeout")
self.assertFalse(client.uses_direct_window)
self.assertEqual(client.get_status()["error"]["code"], "connect_timeout")
self.assertEqual(direct.call_count, 2)
def test_direct_pause_is_needed_only_for_running_winws2(self) -> None:
from winws_runtime.runtime.direct_network import direct_network_pause_needed
running = Mock()
running.is_running.return_value = True
stopped = Mock()
stopped.is_running.return_value = False
winws1 = Mock(spec=["is_running"])
winws1.is_running.return_value = True
for runner, expected in ((running, True), (stopped, False), (winws1, False), (None, False)):
with self.subTest(expected=expected), patch(
"winws_runtime.runners.runner_factory.get_current_runner",
return_value=runner,
):
self.assertEqual(direct_network_pause_needed(), expected)
def test_direct_boundary_uses_active_winws2_owner(self) -> None:
from winws_runtime.runtime.direct_network import run_with_direct_network_access
runner = Mock()
runner.run_with_direct_network_access.side_effect = lambda operation: operation()
operation = Mock(return_value="ok")
with patch(
"winws_runtime.runners.runner_factory.get_current_runner",
return_value=runner,
):
self.assertEqual(run_with_direct_network_access(operation), "ok")
runner.run_with_direct_network_access.assert_called_once()
operation.assert_called_once_with()
def test_winws2_owner_restores_exact_preset_after_request(self) -> None:
from winws_runtime.runners.zapret2_runner import Winws2StrategyRunner
runner = object.__new__(Winws2StrategyRunner)
runner._operation_lock = threading.RLock()
runner.running_process = object()
runner._preset_file_path = __file__
runner.current_launch_label = "Точный preset"
runner.is_running = Mock(return_value=True)
runner._stop_process_only_locked = Mock(return_value=True)
runner._start_from_preset_file_locked = Mock(return_value=True)
operation = Mock(return_value="response")
self.assertEqual(runner.run_with_direct_network_access(operation), "response")
runner._stop_process_only_locked.assert_called_once_with()
runner._start_from_preset_file_locked.assert_called_once_with(
__file__,
"Точный preset",
force_cleanup=False,
retry_count=0,
stable_start_window_seconds=0.3,
)
def test_restore_failure_is_returned_as_typed_premium_error(self) -> None:
from donater.api import PremiumApiClient
from winws_runtime.runtime.direct_network import DirectNetworkAccessError
client = PremiumApiClient(base_url="https://premium.example/api")
client._session = Mock()
with patch(
"donater.api.request_with_dns_fallback",
side_effect=requests.exceptions.SSLError("desync broke tls"),
), patch(
"winws_runtime.runtime.direct_network.direct_network_pause_needed",
return_value=True,
), patch(
"winws_runtime.runtime.direct_network.run_with_direct_network_access",
side_effect=DirectNetworkAccessError("restore failed"),
):
result = client.get_status()
self.assertEqual(result["error"]["code"], "winws_restore_failed")
self.assertFalse(result["error"]["retryable"])
def test_api_classifies_tls_dns_and_timeout_errors(self) -> None:
from donater.api import PremiumApiClient
cases = (
(requests.exceptions.SSLError("certificate"), "tls_error"),
(requests.exceptions.ConnectTimeout("connect"), "connect_timeout"),
(requests.exceptions.ReadTimeout("read"), "read_timeout"),
)
for error, expected_code in cases:
with self.subTest(expected_code=expected_code):
client = PremiumApiClient(base_url="https://premium.example/api")
client._session = Mock()
client._session.request.side_effect = error
with patch(
"winws_runtime.runtime.direct_network.run_with_direct_network_access",
side_effect=lambda operation: operation(),
), patch(
"utils.https_dns_fallback.resolve_hostname_via_doh",
return_value=(),
):
result = client.get_status()
self.assertEqual(result["error"]["code"], expected_code)
dns_error = requests.exceptions.ConnectionError("dns")
dns_error.__cause__ = socket.gaierror(-2, "name resolution")
client = PremiumApiClient(base_url="https://premium.example/api")
client._session = Mock()
client._session.request.side_effect = dns_error
with patch(
"winws_runtime.runtime.direct_network.run_with_direct_network_access",
side_effect=lambda operation: operation(),
), patch(
"utils.https_dns_fallback.resolve_hostname_via_doh",
return_value=(),
):
result = client.get_status()
self.assertEqual(result["error"]["code"], "dns_error")
def test_premium_uses_shared_dynamic_dns_transport(self) -> None:
from donater.api import PremiumApiClient
response = Mock()
response.content = b'{"success": true}'
response.status_code = 200
response.json.return_value = {"success": True}
client = PremiumApiClient(base_url="https://premium.example/api")
with patch(
"donater.api.request_with_dns_fallback",
return_value=response,
) as request:
result = client.get_status()
self.assertTrue(result["success"])
request.assert_called_once_with(
client._session,
"GET",
"https://premium.example/api/status",
json=None,
timeout=client.health_timeout,
)
if __name__ == "__main__":
unittest.main()