Skip to content

Commit 128f38d

Browse files
committed
test: add error handling tests to improve coverage
Added 12 new tests for error cases and edge scenarios: - Plugin registration with invalid types - Child switch management errors - Switcher methods (describe, to_spec, iter_plugins) - Plugin configuration updates Coverage improved from 90% to 93%: - core.py: 90% → 94% - Total: 90% → 93% Test count: 83 → 95 tests
1 parent f64fb45 commit 128f38d

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

tests/test_error_cases.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""
2+
Tests for error handling and edge cases.
3+
4+
These tests target uncovered error paths to improve coverage.
5+
"""
6+
7+
from __future__ import annotations
8+
9+
import unittest
10+
11+
from smartswitch import BasePlugin, Switcher
12+
13+
14+
class TestPluginErrorHandling(unittest.TestCase):
15+
"""Test error handling in plugin registration."""
16+
17+
def test_register_non_plugin_class(self):
18+
"""Test that registering non-BasePlugin class raises TypeError."""
19+
20+
class NotAPlugin:
21+
pass
22+
23+
with self.assertRaises(TypeError) as cm:
24+
Switcher.register_plugin("invalid", NotAPlugin)
25+
26+
self.assertIn("BasePlugin subclass", str(cm.exception))
27+
28+
def test_plug_with_unknown_plugin_name(self):
29+
"""Test that plug() with unknown name raises ValueError."""
30+
sw = Switcher()
31+
32+
# String that is not a registered plugin name
33+
with self.assertRaises(ValueError) as cm:
34+
sw.plug("not_a_plugin")
35+
36+
self.assertIn("Unknown plugin name", str(cm.exception))
37+
38+
def test_plug_with_invalid_class(self):
39+
"""Test that plug() with non-plugin class raises TypeError."""
40+
sw = Switcher()
41+
42+
class NotAPlugin:
43+
pass
44+
45+
with self.assertRaises(TypeError) as cm:
46+
sw.plug(NotAPlugin)
47+
48+
self.assertIn("BasePlugin", str(cm.exception))
49+
50+
51+
class TestSwitcherChildErrors(unittest.TestCase):
52+
"""Test error handling in child switch management."""
53+
54+
def test_get_nonexistent_child(self):
55+
"""Test that getting nonexistent child raises KeyError."""
56+
sw = Switcher("parent")
57+
58+
with self.assertRaises(KeyError) as cm:
59+
sw.get_child("nonexistent")
60+
61+
self.assertIn("No child switch named 'nonexistent'", str(cm.exception))
62+
63+
def test_add_switch_to_itself(self):
64+
"""Test that adding switch to itself raises ValueError."""
65+
sw = Switcher("self")
66+
67+
with self.assertRaises(ValueError) as cm:
68+
sw.add_child(sw)
69+
70+
self.assertIn("Cannot attach a switch to itself", str(cm.exception))
71+
72+
def test_child_name_collision(self):
73+
"""Test that duplicate child name raises ValueError."""
74+
parent = Switcher("parent")
75+
child1 = Switcher("child")
76+
child2 = Switcher("another")
77+
78+
parent.add_child(child1, name="duplicate")
79+
80+
with self.assertRaises(ValueError) as cm:
81+
parent.add_child(child2, name="duplicate")
82+
83+
self.assertIn("Child name collision", str(cm.exception))
84+
85+
86+
class TestSwitcherMethods(unittest.TestCase):
87+
"""Test uncovered Switcher methods."""
88+
89+
def test_describe(self):
90+
"""Test describe() method returns correct structure."""
91+
sw = Switcher("test")
92+
93+
@sw
94+
def handler(x):
95+
return x * 2
96+
97+
desc = sw.describe()
98+
99+
self.assertEqual(desc["name"], "test")
100+
self.assertIn("methods", desc)
101+
self.assertIn("handler", desc["methods"])
102+
self.assertIn("children", desc)
103+
self.assertIn("plugins", desc)
104+
105+
def test_plugin_to_spec(self):
106+
"""Test plugin to_spec() method."""
107+
108+
class TestPlugin(BasePlugin):
109+
def wrap_handler(self, switch, entry, call_next):
110+
return call_next
111+
112+
plugin = TestPlugin(name="test", custom_param="value")
113+
spec = plugin.to_spec()
114+
115+
self.assertEqual(spec.factory, TestPlugin)
116+
self.assertEqual(spec.plugin_name, "test")
117+
self.assertEqual(spec.kwargs["custom_param"], "value")
118+
119+
def test_registered_plugins(self):
120+
"""Test registered_plugins() returns dict."""
121+
registry = Switcher.registered_plugins()
122+
123+
self.assertIsInstance(registry, dict)
124+
# Should have at least 'logging' plugin registered
125+
self.assertIn("logging", registry)
126+
127+
def test_iter_plugins_empty(self):
128+
"""Test iter_plugins() on Switcher without plugins."""
129+
sw = Switcher()
130+
131+
plugins = list(sw.iter_plugins())
132+
self.assertEqual(len(plugins), 0)
133+
134+
def test_iter_plugins_with_plugins(self):
135+
"""Test iter_plugins() returns all plugins."""
136+
137+
class Plugin1(BasePlugin):
138+
def wrap_handler(self, switch, entry, call_next):
139+
return call_next
140+
141+
class Plugin2(BasePlugin):
142+
def wrap_handler(self, switch, entry, call_next):
143+
return call_next
144+
145+
sw = Switcher()
146+
sw.plug(Plugin1, name="p1")
147+
sw.plug(Plugin2, name="p2")
148+
149+
plugins = list(sw.iter_plugins())
150+
self.assertEqual(len(plugins), 2)
151+
names = [p.name for p in plugins]
152+
self.assertIn("p1", names)
153+
self.assertIn("p2", names)
154+
155+
156+
class TestPluginConfiguration(unittest.TestCase):
157+
"""Test plugin configuration methods."""
158+
159+
def test_plugin_config_update(self):
160+
"""Test that plugging instance updates its config."""
161+
162+
class ConfigPlugin(BasePlugin):
163+
def wrap_handler(self, switch, entry, call_next):
164+
return call_next
165+
166+
# Create plugin with initial config
167+
plugin = ConfigPlugin(name="test", param1="value1")
168+
self.assertEqual(plugin.config["param1"], "value1")
169+
170+
# Plug it with additional config
171+
sw = Switcher()
172+
sw.plug(plugin, param2="value2")
173+
174+
# Config should be updated
175+
self.assertEqual(plugin.config["param1"], "value1")
176+
self.assertEqual(plugin.config["param2"], "value2")
177+
178+
179+
if __name__ == "__main__":
180+
unittest.main()

0 commit comments

Comments
 (0)