Files
AnabasisChatRemove/tests/test_main_contracts.py
Денисов Александр Андреевич 0a82ad7e3e
Some checks are pending
Desktop Release / release (push) Waiting to run
Desktop CI / tests (push) Successful in 1m51s
Add VK callback auth support and admin demotion
2026-06-05 19:01:52 +03:00

161 lines
6.6 KiB
Python

import ast
import unittest
from pathlib import Path
class MainContractsTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.main_source = Path("main.py").read_text(encoding="utf-8-sig")
cls.module = ast.parse(cls.main_source)
cls.vk_chat_manager = cls._find_class("VkChatManager")
@classmethod
def _find_class(cls, class_name):
for node in cls.module.body:
if isinstance(node, ast.ClassDef) and node.name == class_name:
return node
raise AssertionError(f"Class {class_name} not found")
def _find_method(self, method_name):
for node in self.vk_chat_manager.body:
if isinstance(node, ast.FunctionDef) and node.name == method_name:
return node
self.fail(f"Method {method_name} not found")
def _iter_nodes(self, node):
return ast.walk(node)
def test_auth_error_contexts_contains_only_supported_contexts(self):
expected_contexts = {"load_chats", "execute_user_action", "set_user_admin", "unset_user_admin"}
for node in self.module.body:
if isinstance(node, ast.Assign):
for target in node.targets:
if isinstance(target, ast.Name) and target.id == "AUTH_ERROR_CONTEXTS":
actual = set(ast.literal_eval(node.value))
self.assertSetEqual(actual, expected_contexts)
return
self.fail("AUTH_ERROR_CONTEXTS assignment not found")
def test_unset_user_admin_method_exists(self):
self._find_method("unset_user_admin")
def test_uses_custom_standalone_vk_app(self):
constants = {}
for node in self.module.body:
if not isinstance(node, ast.Assign) or len(node.targets) != 1:
continue
target = node.targets[0]
if isinstance(target, ast.Name):
try:
constants[target.id] = ast.literal_eval(node.value)
except Exception:
continue
self.assertEqual(constants.get("VK_APP_ID"), "54454043")
self.assertIn("ANABASIS_VK_REDIRECT_URI", self.main_source)
self.assertIn("https://vk.daemonlord.ru/vk/callback", self.main_source)
def test_remove_action_demotes_member_before_removing(self):
bulk_worker = None
for node in self.module.body:
if isinstance(node, ast.ClassDef) and node.name == "BulkActionWorker":
bulk_worker = node
break
self.assertIsNotNone(bulk_worker, "BulkActionWorker class not found")
run_method = None
for node in bulk_worker.body:
if isinstance(node, ast.FunctionDef) and node.name == "run":
run_method = node
break
self.assertIsNotNone(run_method, "BulkActionWorker.run method not found")
has_member_role = False
has_remove_call = False
for node in ast.walk(run_method):
if isinstance(node, ast.keyword) and node.arg == "role":
if isinstance(node.value, ast.Constant) and node.value.value == "member":
has_member_role = True
if isinstance(node, ast.Attribute) and node.attr == "removeChatUser":
has_remove_call = True
self.assertTrue(has_member_role, "remove action must demote admins with role='member'")
self.assertTrue(has_remove_call, "remove action must still call removeChatUser")
def test_check_for_updates_has_reentry_guard(self):
method = self._find_method("check_for_updates")
has_guard = False
for node in method.body:
if not isinstance(node, ast.If):
continue
test = node.test
if (
isinstance(test, ast.Attribute)
and isinstance(test.value, ast.Name)
and test.value.id == "self"
and test.attr == "_update_in_progress"
):
has_guard = any(isinstance(stmt, ast.Return) for stmt in node.body)
if has_guard:
break
self.assertTrue(has_guard, "check_for_updates must return when update is already in progress")
def test_check_for_updates_connects_thread_finish_handler(self):
method = self._find_method("check_for_updates")
for node in self._iter_nodes(method):
if not isinstance(node, ast.Call):
continue
func = node.func
if not (isinstance(func, ast.Attribute) and func.attr == "connect"):
continue
value = func.value
if not (
isinstance(value, ast.Attribute)
and value.attr == "finished"
and isinstance(value.value, ast.Attribute)
and value.value.attr == "update_thread"
and isinstance(value.value.value, ast.Name)
and value.value.value.id == "self"
):
continue
if len(node.args) != 1:
continue
arg = node.args[0]
if (
isinstance(arg, ast.Attribute)
and arg.attr == "_on_update_thread_finished"
and isinstance(arg.value, ast.Name)
and arg.value.id == "self"
):
return
self.fail("update_thread.finished must be connected to _on_update_thread_finished")
def test_on_update_thread_finished_clears_update_state(self):
method = self._find_method("_on_update_thread_finished")
assignments = {}
for node in method.body:
if not isinstance(node, ast.Assign) or len(node.targets) != 1:
continue
target = node.targets[0]
if (
isinstance(target, ast.Attribute)
and isinstance(target.value, ast.Name)
and target.value.id == "self"
):
assignments[target.attr] = node.value
self.assertIn("_update_in_progress", assignments)
self.assertIn("update_checker", assignments)
self.assertIn("update_thread", assignments)
self.assertIsInstance(assignments["_update_in_progress"], ast.Constant)
self.assertIs(assignments["_update_in_progress"].value, False)
self.assertIsInstance(assignments["update_checker"], ast.Constant)
self.assertIsNone(assignments["update_checker"].value)
self.assertIsInstance(assignments["update_thread"], ast.Constant)
self.assertIsNone(assignments["update_thread"].value)
if __name__ == "__main__":
unittest.main()