diff --git a/tests/test_permissions.py b/tests/test_permissions.py new file mode 100644 index 0000000..bc3023e --- /dev/null +++ b/tests/test_permissions.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import unittest + +from src.permissions import ToolPermissionContext + + +class TestToolPermissionContext(unittest.TestCase): + # 1. Empty context blocks nothing + def test_empty_context_blocks_nothing(self) -> None: + ctx = ToolPermissionContext() + self.assertFalse(ctx.blocks("anything")) + self.assertFalse(ctx.blocks("")) + + # 2. Exact name blocking (case insensitive) + def test_exact_name_blocking(self) -> None: + ctx = ToolPermissionContext.from_iterables(deny_names=["dangerous_tool"]) + self.assertTrue(ctx.blocks("dangerous_tool")) + self.assertTrue(ctx.blocks("Dangerous_Tool")) + self.assertTrue(ctx.blocks("DANGEROUS_TOOL")) + self.assertFalse(ctx.blocks("safe_tool")) + + # 3. Prefix blocking (case insensitive) + def test_prefix_blocking(self) -> None: + ctx = ToolPermissionContext.from_iterables(deny_prefixes=["admin_"]) + self.assertTrue(ctx.blocks("admin_delete")) + self.assertTrue(ctx.blocks("Admin_Delete")) + self.assertTrue(ctx.blocks("ADMIN_CREATE")) + self.assertFalse(ctx.blocks("user_admin")) + + # 4. Combined name + prefix blocking + def test_combined_name_and_prefix_blocking(self) -> None: + ctx = ToolPermissionContext.from_iterables( + deny_names=["rm"], + deny_prefixes=["sudo_"], + ) + self.assertTrue(ctx.blocks("rm")) + self.assertTrue(ctx.blocks("sudo_restart")) + self.assertFalse(ctx.blocks("ls")) + + # 5. Non-matching names are allowed + def test_non_matching_names_allowed(self) -> None: + ctx = ToolPermissionContext.from_iterables( + deny_names=["blocked"], + deny_prefixes=["bad_"], + ) + self.assertFalse(ctx.blocks("allowed")) + self.assertFalse(ctx.blocks("good_tool")) + self.assertFalse(ctx.blocks("not_bad")) + + # 6. from_iterables with None args + def test_from_iterables_none_args(self) -> None: + ctx = ToolPermissionContext.from_iterables(deny_names=None, deny_prefixes=None) + self.assertEqual(ctx.deny_names, frozenset()) + self.assertEqual(ctx.deny_prefixes, ()) + self.assertFalse(ctx.blocks("anything")) + + def test_from_iterables_default_args(self) -> None: + ctx = ToolPermissionContext.from_iterables() + self.assertEqual(ctx.deny_names, frozenset()) + self.assertEqual(ctx.deny_prefixes, ()) + + # 7. from_iterables normalizes to lowercase + def test_from_iterables_normalizes_to_lowercase(self) -> None: + ctx = ToolPermissionContext.from_iterables( + deny_names=["FooBar"], + deny_prefixes=["PFX_"], + ) + self.assertIn("foobar", ctx.deny_names) + self.assertNotIn("FooBar", ctx.deny_names) + self.assertEqual(ctx.deny_prefixes, ("pfx_",)) + self.assertTrue(ctx.blocks("FOOBAR")) + self.assertTrue(ctx.blocks("pfx_something")) + + # 8. Multiple deny_names + def test_multiple_deny_names(self) -> None: + ctx = ToolPermissionContext.from_iterables( + deny_names=["tool_a", "tool_b", "tool_c"], + ) + self.assertTrue(ctx.blocks("tool_a")) + self.assertTrue(ctx.blocks("tool_b")) + self.assertTrue(ctx.blocks("tool_c")) + self.assertFalse(ctx.blocks("tool_d")) + + +if __name__ == "__main__": + unittest.main()