"""Инварианты графа маршрутов и выбор полезной нагрузки при пересылке.""" import asyncio from copy import deepcopy import pytest @pytest.fixture def router(): """Создаёт настоящий Router независимо от менеджера и сессий.""" from router import Router return Router() class Node: def __init__(self, last_data=None): self.last_data = last_data or {} self.sent = [] def send(self, packet): self.sent.append(deepcopy(packet)) def test_add_creates_empty_routes_and_preserves_existing_edges(router): """Проверяет создание множества маршрутов и сохранение связей при повторном add.""" router.add(1) assert router.session_routes[1] == set() router.session_routes[1].add(2) router.add(1) assert router.session_routes[1] == {2} def test_move_rewrites_only_edges_pointing_to_old_uid(router): """Проверяет перенос исходящих/входящих связей без изменения несвязанных маршрутов.""" router.session_routes = {1: {2}, 2: {1, 4}, 3: set(), 4: {2}, 5: set()} router.move(1, 5) assert router.session_routes == {2: {5, 4}, 3: set(), 4: {2}, 5: {2}} def test_move_handles_self_route(router): """Проверяет замену UID в маршруте, который указывал на собственную сессию.""" router.session_routes = {1: {1, 2}, 2: {1}} router.move(1, 3) assert router.session_routes == {2: {3}, 3: {3, 2}} def test_move_missing_uid_does_not_change_graph(router): """Проверяет безопасный перенос отсутствующего UID без добавления новых связей.""" router.session_routes = {1: {2}, 2: set()} before = deepcopy(router.session_routes) router.move(99, 100) assert router.session_routes == before def test_remove_clears_outgoing_and_incoming_edges(router): """Проверяет удаление узла и всех ссылок на него после окончательной очистки.""" router.session_routes = {1: {2}, 2: {1, 3}, 3: {1}, 4: set()} router.remove(1) assert router.session_routes == {2: {3}, 3: set(), 4: set()} def test_remove_missing_uid_is_safe(router): """Проверяет повторное удаление уже отсутствующего UID.""" router.session_routes = {1: set()} router.remove(99) assert router.session_routes == {1: set()} def test_process_delivers_source_payload_to_each_target(router): """Проверяет fan-out, данные отправителя и отсутствие доставки несвязанным узлам.""" nodes = { 1: Node({"telemetry": {"battery": 87}}), 2: Node({"telemetry": {"battery": 1}}), 3: Node(), 4: Node(), } router.sessions = nodes router.session_routes = {1: {2, 3}, 2: set(), 3: set(), 4: set()} asyncio.run(router.process(1, "telemetry")) expected = [{"command": "telemetry", "payload": {"battery": 87}}] assert nodes[2].sent == expected assert nodes[3].sent == expected assert nodes[1].sent == nodes[4].sent == [] def test_process_with_no_routes_sends_nothing(router): """Проверяет отсутствие доставки при пустом множестве получателей.""" router.sessions = {1: Node({"telemetry": {"battery": 87}}), 2: Node()} router.session_routes = {1: set(), 2: set()} asyncio.run(router.process(1, "telemetry")) assert router.sessions[1].sent == router.sessions[2].sent == []