zapretgui/tests/test_registry.py
loop-uh ea7b618c38
All checks were successful
Zapret source guards / validate (push) Successful in 10s
Windows-сборка / Dev Dev 21.1.7.188 опубликован
Единый источник слов для всех проверок, маскирование внешнего адреса, видеоузлы YouTube вашей сети
block_view решает строки и итоги сети, компьютера, хостингов, звонков, Telegram, DNS, скорости, места фильтра и сравнения: карточки и текстовый отчёт берут их оттуда, а журнал строит движок из того же словаря. Внешний адрес в отчёте скрыт (109.252.1.xxx), провайдер и сеть остаются. Видеоузлы берутся со страницы ролика и от redirector.googlevideo.com, у узла названа его сеть, а медленный первый узел сравнивается с запасным.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 11:02:18 -04:00

232 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 tempfile
import unittest
from pathlib import Path
from blockcheck.ui.result_cards_model import build_cards
from diagnostics import block_view, net_access, registry
HOSTS = "\n".join(['"quoted.example', "facebook.com", "www.linkedin.com", "discordapp.com", "FACEBOOK.com", "localhost"])
IPS = "68.171.224.0/19\n203.0.113.7\n# заметка\nне адрес\n2001:db8::/32\n"
DAY = registry.MAX_AGE_S
def _big(extra: str = "") -> bytes:
"""Список правдоподобного размера: маленький файл считается ошибкой скачивания."""
filler = "\n".join(f"site{number}.example" for number in range(registry._MIN_HOSTS))
return f"{HOSTS}\n{extra}\n{filler}\n".encode()
class _Server:
def __init__(self, hosts: bytes | None = None, ips: bytes | None = IPS.encode(), etag: str = "v1") -> None:
self.hosts, self.ips, self.etag = hosts if hosts is not None else _big(), ips, etag
self.asked: list[tuple[str, str]] = []
def __call__(self, url: str, etag: str):
self.asked.append((url.rsplit("/", 1)[-1], etag))
body = self.hosts if url == registry.HOSTS_URL else self.ips
if body is None:
return None
if etag and etag == self.etag:
return registry.Fetched(unchanged=True, etag=etag)
return registry.Fetched(body=body, etag=self.etag)
class IndexTests(unittest.TestCase):
def setUp(self) -> None:
hosts, count = registry.build_hosts(HOSTS.splitlines())
self.count = count
self.index = registry.Index(hosts=hosts, networks=tuple(registry.build_networks(IPS.splitlines())), updated=1000.0)
def test_names_are_cleaned_and_counted_once(self) -> None:
# Кавычка в начале строки убрана, регистр не важен, «localhost» без точки — не сайт.
self.assertEqual(self.count, 4)
self.assertEqual(self.index.find_host("quoted.example"), "quoted.example")
def test_site_is_found_by_itself_or_by_a_domain_above(self) -> None:
self.assertEqual(self.index.find_host("facebook.com"), "facebook.com")
self.assertEqual(self.index.find_host("WWW.Facebook.com."), "facebook.com")
self.assertEqual(self.index.find_host("cdn.discordapp.com"), "discordapp.com")
self.assertEqual(self.index.find_host("www.linkedin.com"), "www.linkedin.com")
def test_listed_subdomain_does_not_mark_the_whole_domain(self) -> None:
self.assertEqual(self.index.find_host("linkedin.com"), "")
self.assertEqual(self.index.find_host("x.com"), "")
# Сама зона («com») в реестре не ищется.
self.assertEqual(registry.Index(hosts=registry.build_hosts(["a.com"])[0]).find_host("b.com"), "")
def test_address_is_found_inside_a_blocked_network(self) -> None:
self.assertEqual(self.index.find_ip("68.171.230.1"), "68.171.224.0/19")
self.assertEqual(self.index.find_ip("203.0.113.7"), "203.0.113.7")
self.assertEqual(self.index.find_ip("203.0.113.8"), "")
self.assertEqual(self.index.find_ip("8.8.8.8"), "")
self.assertEqual(self.index.find_ip("2001:db8::1"), "")
self.assertEqual(self.index.find_ip("не адрес"), "")
def test_empty_list_finds_nothing_and_says_it_is_missing(self) -> None:
empty = registry.Index()
self.assertEqual((empty.find_host("facebook.com"), empty.find_ip("68.171.230.1")), ("", ""))
self.assertEqual(empty.state(), registry.STATE_MISSING)
self.assertEqual(self.index.state(now=1000.0 + DAY), registry.STATE_FRESH)
self.assertEqual(self.index.state(now=1000.0 + 8 * DAY), registry.STATE_STALE)
class RefreshTests(unittest.TestCase):
def setUp(self) -> None:
self.folder = Path(self.enterContext(tempfile.TemporaryDirectory())) / "registry"
def test_first_run_downloads_and_the_list_survives_on_disk(self) -> None:
index = registry.refresh(self.folder, _Server(), now=1000.0)
self.assertEqual(index.find_host("www.facebook.com"), "facebook.com")
self.assertEqual(index.find_ip("68.171.230.1"), "68.171.224.0/19")
self.assertEqual((index.updated, index.state(now=1000.0)), (1000.0, registry.STATE_FRESH))
self.assertEqual(registry.load(self.folder).count, index.count)
def test_nothing_is_downloaded_twice_within_a_day(self) -> None:
server = _Server()
registry.refresh(self.folder, server, now=1000.0)
server.asked.clear()
registry.refresh(self.folder, server, now=1000.0 + DAY - 1)
self.assertEqual(server.asked, [])
def test_next_day_asks_with_the_version_mark_and_keeps_an_unchanged_list(self) -> None:
server = _Server()
registry.refresh(self.folder, server, now=1000.0)
server.asked.clear()
index = registry.refresh(self.folder, server, now=1000.0 + DAY + 1)
self.assertEqual(server.asked, [("reestr_hostname.txt", "v1"), ("reestr_ipban4.txt", "v1")])
# Список тот же: дата скачивания прежняя, сверка — новая.
self.assertEqual((index.updated, index.checked), (1000.0, 1000.0 + DAY + 1))
def test_changed_list_replaces_the_old_one(self) -> None:
registry.refresh(self.folder, _Server(), now=1000.0)
index = registry.refresh(self.folder, _Server(hosts=_big("newsite.example"), etag="v2"), now=1000.0 + DAY + 1)
self.assertEqual(index.find_host("newsite.example"), "newsite.example")
self.assertEqual(index.updated, 1000.0 + DAY + 1)
def test_failed_download_keeps_the_previous_list(self) -> None:
registry.refresh(self.folder, _Server(), now=1000.0)
broken = _Server()
broken.hosts = None
index = registry.refresh(self.folder, broken, now=1000.0 + 3 * DAY)
self.assertEqual(index.find_host("facebook.com"), "facebook.com")
def test_error_page_instead_of_the_list_is_not_taken_for_the_list(self) -> None:
registry.refresh(self.folder, _Server(), now=1000.0)
index = registry.refresh(self.folder, _Server(hosts=b"<html>500</html>\nfoo.example\n", etag="v2"), now=1000.0 + 3 * DAY)
self.assertEqual(index.find_host("facebook.com"), "facebook.com")
self.assertEqual(registry.refresh(self.folder / "other", _Server(hosts=b"oops"), now=1.0).state(), registry.STATE_MISSING)
def test_damaged_files_give_an_empty_list_not_a_crash(self) -> None:
registry.refresh(self.folder, _Server(), now=1000.0)
(self.folder / "hosts.idx").write_bytes(b"abc")
self.assertEqual(registry.load(self.folder).count, 0)
self.assertEqual(registry.load(self.folder / "nowhere").count, 0)
class ReportTests(unittest.TestCase):
def setUp(self) -> None:
self.index = registry.Index(
hosts=registry.build_hosts(HOSTS.splitlines())[0],
networks=tuple(registry.build_networks(IPS.splitlines())),
updated=1000.0,
)
self.services = [
{
"key": "fb",
"label": "Facebook",
"level": "fail",
"targets": [{"host": "www.facebook.com", "address": "68.171.230.1", "ok": False, "main": True}],
},
{"key": "x", "label": "X", "level": "fail", "targets": [{"host": "x.com", "address": "1.2.3.4", "ok": False, "main": True}]},
{"key": "vk", "label": "VK", "level": "ok", "targets": [{"host": "vk.com", "address": "5.6.7.8", "ok": True, "main": True}]},
]
def test_report_gets_a_mark_for_every_site(self) -> None:
self.services = registry.annotate(self.services, self.index)
marks = [service["targets"][0]["registry"] for service in self.services]
self.assertEqual(marks[0], {"listed": True, "name": "facebook.com", "network": "68.171.224.0/19"})
self.assertEqual(marks[1], {"listed": False, "name": "", "network": ""})
def test_without_a_list_nothing_is_claimed(self) -> None:
self.services = registry.annotate(self.services, registry.Index())
self.assertNotIn("registry", self.services[0]["targets"][0])
self.assertIn("скачать не удалось", "\n".join(block_view.text_lines("registry", registry.summary(registry.Index()))))
cards = {card.title: card for card in build_cards({"services": self.services})}
self.assertNotIn(("в реестре РКН", "info"), cards["Facebook"].chips)
def test_text_report_names_listed_sites_and_warns_about_blocks_outside_the_registry(self) -> None:
from diagnostics import service_view
self.services = registry.annotate(self.services, self.index)
facebook = "\n".join(service_view.render_text(service_view.service_sections(self.services[0])))
text = "\n".join(block_view.text_lines("registry", registry.summary(self.index, now=1000.0)))
# Что про конкретный сайт — в строках сайта, что про сам список — в разделе реестра.
self.assertIn("Реестр РКН: сайт значится в реестре", facebook.replace("в реестре значится facebook.com", "сайт значится в реестре"))
self.assertIn("68.171.224.0/19", facebook)
self.assertIn("и без записи в реестре", text)
stale = "\n".join(block_view.text_lines("registry", registry.summary(self.index, now=1000.0 + 9 * DAY)))
self.assertIn("давно не обновлялся", stale)
def test_annotating_does_not_touch_the_report_the_screen_already_has(self) -> None:
# Экран получает отчёт по сайтам раньше, чем скачается реестр, и читает его из
# своего потока: отметка не должна дописываться в уже отданные записи.
marked = registry.annotate(self.services, self.index)
self.assertNotIn("registry", self.services[0]["targets"][0])
self.assertNotIn("tags", self.services[0])
self.assertTrue(marked[0]["targets"][0]["registry"]["listed"])
def test_cards_show_the_mark_and_do_not_call_an_unlisted_site_unblocked(self) -> None:
self.services = registry.annotate(self.services, self.index)
cards = {card.title: card for card in build_cards({"services": self.services})}
def row(title: str) -> str:
return next(line.text for line in cards[title].sections[0].lines if line.name == "Реестр РКН")
self.assertIn(("в реестре РКН", "info"), cards["Facebook"].chips)
self.assertNotIn(("в реестре РКН", "info"), cards["X"].chips)
self.assertIn("в реестре значится facebook.com", row("Facebook"))
self.assertEqual(row("X"), "не значится — блокировать могут и без записи в реестре")
self.assertEqual(row("VK"), "не значится")
class DownloadTests(unittest.TestCase):
def test_unreachable_server_is_none_not_an_error(self) -> None:
self.assertIsNone(net_access.download_file("http://127.0.0.1:9/nothing", timeout=0.5))
if __name__ == "__main__":
unittest.main()
class BuildOrderTests(unittest.TestCase):
def test_index_built_in_buckets_is_sorted_and_without_repeats(self) -> None:
# Список собирается кучками по первому байту отпечатка, чтобы не подвешивать окно;
# итог обязан быть тем же, что у одной общей сортировки, — по нему идёт двоичный поиск.
names = [f"site-{number}.example" for number in range(3000)]
index, count = registry.build_hosts([*names, *names[:500], " SITE-7.example. ", "без-точки"])
keys = [index[at : at + 8] for at in range(0, len(index), 8)]
self.assertEqual(count, 3000)
self.assertEqual(keys, sorted(set(keys)))
self.assertEqual(len(keys), 3000)