Files
connpy/connpy/tests/test_plugins.py
T
fluzzi32 cf95befb43 test: add comprehensive unit tests and fix bare excepts
- Added comprehensive unit test suite covering AI, core, plugins, completion, API, and hooks using pytest.
- Replaced bare 'except:' clauses across the codebase with specific exception handling (e.g., ValueError, KeyError, OSError) to prevent swallowing system exit calls.
- Fixed Python 3.8+ AST compatibility in plugins.py (support for ast.Constant).
- Removed deprecated pkg_resources import from __init__.py.
- Fixed missing 'printer' import in configfile.py that caused NameErrors during save failures.
2026-04-03 17:11:45 -03:00

328 lines
9.7 KiB
Python

"""Tests for connpy.plugins module."""
import os
import textwrap
import pytest
from connpy.plugins import Plugins
# ---------------------------------------------------------------------------
# Helper: write a plugin script to a file
# ---------------------------------------------------------------------------
def _write_plugin(path, code):
"""Write dedented code to a file."""
with open(path, "w") as f:
f.write(textwrap.dedent(code))
# =========================================================================
# verify_script tests
# =========================================================================
class TestVerifyScript:
def test_valid_parser_entrypoint(self, tmp_path):
p = tmp_path / "good.py"
_write_plugin(p, """\
import argparse
class Parser:
def __init__(self):
self.parser = argparse.ArgumentParser()
class Entrypoint:
def __init__(self, args, parser, connapp):
pass
""")
plugins = Plugins()
assert plugins.verify_script(str(p)) == False
def test_valid_preload_only(self, tmp_path):
p = tmp_path / "preload.py"
_write_plugin(p, """\
class Preload:
def __init__(self, connapp):
pass
""")
plugins = Plugins()
assert plugins.verify_script(str(p)) == False
def test_valid_all_three(self, tmp_path):
p = tmp_path / "all.py"
_write_plugin(p, """\
import argparse
class Parser:
def __init__(self):
self.parser = argparse.ArgumentParser()
class Entrypoint:
def __init__(self, args, parser, connapp):
pass
class Preload:
def __init__(self, connapp):
pass
""")
plugins = Plugins()
assert plugins.verify_script(str(p)) == False
def test_parser_without_entrypoint(self, tmp_path):
p = tmp_path / "bad.py"
_write_plugin(p, """\
import argparse
class Parser:
def __init__(self):
self.parser = argparse.ArgumentParser()
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result # Should be a truthy error string
assert "Entrypoint" in result
def test_entrypoint_without_parser(self, tmp_path):
p = tmp_path / "bad.py"
_write_plugin(p, """\
class Entrypoint:
def __init__(self, args, parser, connapp):
pass
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result
assert "Parser" in result
def test_no_valid_class(self, tmp_path):
p = tmp_path / "empty.py"
_write_plugin(p, """\
def some_function():
pass
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result
assert "No valid class" in result
def test_parser_missing_self_parser(self, tmp_path):
p = tmp_path / "bad.py"
_write_plugin(p, """\
class Parser:
def __init__(self):
self.something = "not parser"
class Entrypoint:
def __init__(self, args, parser, connapp):
pass
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result
assert "self.parser" in result
def test_entrypoint_wrong_args(self, tmp_path):
p = tmp_path / "bad.py"
_write_plugin(p, """\
import argparse
class Parser:
def __init__(self):
self.parser = argparse.ArgumentParser()
class Entrypoint:
def __init__(self, args):
pass
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result
assert "Entrypoint" in result
def test_preload_wrong_args(self, tmp_path):
p = tmp_path / "bad.py"
_write_plugin(p, """\
class Preload:
def __init__(self, connapp, extra):
pass
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result
assert "Preload" in result
def test_disallowed_top_level(self, tmp_path):
p = tmp_path / "bad.py"
_write_plugin(p, """\
MY_GLOBAL = "not allowed"
class Preload:
def __init__(self, connapp):
pass
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result
assert "not allowed" in result.lower() or "Plugin can only have" in result
def test_syntax_error(self, tmp_path):
p = tmp_path / "bad.py"
_write_plugin(p, """\
def broken(
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result
assert "Syntax error" in result
def test_if_name_main_allowed(self, tmp_path):
p = tmp_path / "good.py"
_write_plugin(p, """\
class Preload:
def __init__(self, connapp):
pass
if __name__ == "__main__":
print("standalone")
""")
plugins = Plugins()
assert plugins.verify_script(str(p)) == False
def test_other_if_not_allowed(self, tmp_path):
p = tmp_path / "bad.py"
_write_plugin(p, """\
import sys
if sys.platform == "linux":
pass
class Preload:
def __init__(self, connapp):
pass
""")
plugins = Plugins()
result = plugins.verify_script(str(p))
assert result
assert "__name__" in result
# =========================================================================
# Import and loading tests
# =========================================================================
class TestPluginLoading:
def test_import_from_path(self, tmp_path):
p = tmp_path / "mymod.py"
_write_plugin(p, """\
MY_VAR = 42
""")
plugins = Plugins()
module = plugins._import_from_path(str(p))
assert module.MY_VAR == 42
def test_import_plugins_to_argparse(self, tmp_path):
"""Valid plugins get loaded into argparse."""
import argparse
plugin_dir = tmp_path / "plugins"
plugin_dir.mkdir()
_write_plugin(plugin_dir / "myplugin.py", """\
import argparse
class Parser:
def __init__(self):
self.parser = argparse.ArgumentParser(description="My plugin")
class Entrypoint:
def __init__(self, args, parser, connapp):
pass
""")
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
plugins = Plugins()
plugins._import_plugins_to_argparse(str(plugin_dir), subparsers)
assert "myplugin" in plugins.plugins
assert "myplugin" in plugins.plugin_parsers
def test_plugin_name_collision(self, tmp_path):
"""Plugin with same name as existing subcommand is skipped."""
import argparse
plugin_dir = tmp_path / "plugins"
plugin_dir.mkdir()
_write_plugin(plugin_dir / "existcmd.py", """\
import argparse
class Parser:
def __init__(self):
self.parser = argparse.ArgumentParser()
class Entrypoint:
def __init__(self, args, parser, connapp):
pass
""")
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
subparsers.add_parser("existcmd") # Already taken
plugins = Plugins()
plugins._import_plugins_to_argparse(str(plugin_dir), subparsers)
assert "existcmd" not in plugins.plugins
def test_preload_registration(self, tmp_path):
"""Preload class gets registered in preloads dict."""
import argparse
plugin_dir = tmp_path / "plugins"
plugin_dir.mkdir()
_write_plugin(plugin_dir / "preloader.py", """\
class Preload:
def __init__(self, connapp):
pass
""")
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
plugins = Plugins()
plugins._import_plugins_to_argparse(str(plugin_dir), subparsers)
assert "preloader" in plugins.preloads
def test_invalid_plugin_skipped(self, tmp_path, capsys):
"""Invalid plugin is skipped with error message."""
import argparse
plugin_dir = tmp_path / "plugins"
plugin_dir.mkdir()
_write_plugin(plugin_dir / "badplugin.py", """\
MY_GLOBAL = "bad"
""")
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
plugins = Plugins()
plugins._import_plugins_to_argparse(str(plugin_dir), subparsers)
assert "badplugin" not in plugins.plugins
captured = capsys.readouterr()
assert "Failed to load plugin" in captured.err or "Failed to load plugin" in captured.out
def test_empty_directory(self, tmp_path):
"""Empty directory doesn't cause errors."""
import argparse
plugin_dir = tmp_path / "plugins"
plugin_dir.mkdir()
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
plugins = Plugins()
plugins._import_plugins_to_argparse(str(plugin_dir), subparsers)
assert len(plugins.plugins) == 0