1+ import pytest
2+ from abc import ABC , abstractmethod
3+ from typing import Type , Any
4+
5+ from py_spring_core .core .utils import get_unimplemented_abstract_methods
6+
7+
8+ class TestFrameworkUtils :
9+ """Test suite for framework utility functions."""
10+
11+ def test_get_unimplemented_abstract_methods_with_concrete_class (self ):
12+ """Test that fully implemented classes return empty set."""
13+
14+ class AbstractBase (ABC ):
15+ @abstractmethod
16+ def method_a (self ) -> None :
17+ pass
18+
19+ @abstractmethod
20+ def method_b (self ) -> str :
21+ pass
22+
23+ class ConcreteImpl (AbstractBase ):
24+ def method_a (self ) -> None :
25+ pass
26+
27+ def method_b (self ) -> str :
28+ return "implemented"
29+
30+ unimplemented = get_unimplemented_abstract_methods (ConcreteImpl )
31+ assert unimplemented == set ()
32+
33+ def test_get_unimplemented_abstract_methods_with_partial_implementation (self ):
34+ """Test that partially implemented classes return missing methods."""
35+
36+ class AbstractBase (ABC ):
37+ @abstractmethod
38+ def method_a (self ) -> None :
39+ pass
40+
41+ @abstractmethod
42+ def method_b (self ) -> str :
43+ pass
44+
45+ @abstractmethod
46+ def method_c (self ) -> int :
47+ pass
48+
49+ class PartialImpl (AbstractBase ):
50+ def method_a (self ) -> None :
51+ pass
52+
53+ # method_b and method_c are not implemented
54+
55+ unimplemented = get_unimplemented_abstract_methods (PartialImpl )
56+ assert unimplemented == {"method_b" , "method_c" }
57+
58+ def test_get_unimplemented_abstract_methods_with_no_implementation (self ):
59+ """Test that classes with no implementations return all abstract methods."""
60+
61+ class AbstractBase (ABC ):
62+ @abstractmethod
63+ def method_a (self ) -> None :
64+ pass
65+
66+ @abstractmethod
67+ def method_b (self ) -> str :
68+ pass
69+
70+ class NoImpl (AbstractBase ):
71+ # No methods implemented
72+ pass
73+
74+ unimplemented = get_unimplemented_abstract_methods (NoImpl )
75+ assert unimplemented == {"method_a" , "method_b" }
76+
77+ def test_get_unimplemented_abstract_methods_with_multiple_inheritance (self ):
78+ """Test with multiple inheritance from abstract classes."""
79+
80+ class AbstractA (ABC ):
81+ @abstractmethod
82+ def method_a (self ) -> None :
83+ pass
84+
85+ class AbstractB (ABC ):
86+ @abstractmethod
87+ def method_b (self ) -> str :
88+ pass
89+
90+ class MultipleInheritance (AbstractA , AbstractB ):
91+ def method_a (self ) -> None :
92+ pass
93+ # method_b is not implemented
94+
95+ unimplemented = get_unimplemented_abstract_methods (MultipleInheritance )
96+ assert unimplemented == {"method_b" }
97+
98+ def test_get_unimplemented_abstract_methods_with_inheritance_chain (self ):
99+ """Test with inheritance chain where parent implements some methods."""
100+
101+ class AbstractBase (ABC ):
102+ @abstractmethod
103+ def method_a (self ) -> None :
104+ pass
105+
106+ @abstractmethod
107+ def method_b (self ) -> str :
108+ pass
109+
110+ @abstractmethod
111+ def method_c (self ) -> int :
112+ pass
113+
114+ class PartialParent (AbstractBase ):
115+ def method_a (self ) -> None :
116+ pass
117+ # method_b and method_c still abstract
118+
119+ class ChildImpl (PartialParent ):
120+ def method_b (self ) -> str :
121+ return "implemented"
122+ # method_c still not implemented
123+
124+ unimplemented = get_unimplemented_abstract_methods (ChildImpl )
125+ assert unimplemented == {"method_c" }
126+
127+ def test_get_unimplemented_abstract_methods_with_no_abstract_methods (self ):
128+ """Test with class that has no abstract methods."""
129+
130+ class NonAbstractBase (ABC ):
131+ def regular_method (self ) -> None :
132+ pass
133+
134+ class RegularClass (NonAbstractBase ):
135+ def another_method (self ) -> str :
136+ return "normal"
137+
138+ unimplemented = get_unimplemented_abstract_methods (RegularClass )
139+ assert unimplemented == set ()
140+
141+ def test_get_unimplemented_abstract_methods_type_error_non_class (self ):
142+ """Test that function raises TypeError for non-class types."""
143+
144+ with pytest .raises (TypeError , match = "Expected a class type" ):
145+ get_unimplemented_abstract_methods ("not a class" ) # type: ignore
146+
147+ with pytest .raises (TypeError , match = "Expected a class type" ):
148+ get_unimplemented_abstract_methods (42 ) # type: ignore
149+
150+ def test_get_unimplemented_abstract_methods_type_error_non_abc (self ):
151+ """Test that function raises TypeError for non-ABC classes."""
152+
153+ class RegularClass :
154+ def some_method (self ) -> None :
155+ pass
156+
157+ with pytest .raises (TypeError , match = "Expected a subclass of abc.ABC" ):
158+ get_unimplemented_abstract_methods (RegularClass )
159+
160+ def test_get_unimplemented_abstract_methods_with_property_abstracts (self ):
161+ """Test with abstract properties."""
162+
163+ class AbstractWithProperty (ABC ):
164+ @property
165+ @abstractmethod
166+ def abstract_property (self ) -> str :
167+ pass
168+
169+ @abstractmethod
170+ def abstract_method (self ) -> None :
171+ pass
172+
173+ class PartialPropertyImpl (AbstractWithProperty ):
174+ @property
175+ def abstract_property (self ) -> str :
176+ return "implemented"
177+ # abstract_method not implemented
178+
179+ unimplemented = get_unimplemented_abstract_methods (PartialPropertyImpl )
180+ # Properties might be included in the abstract methods set, so we check that abstract_method is there
181+ # and abstract_property is not (since it's implemented)
182+ assert "abstract_method" in unimplemented
183+ assert len ([method for method in unimplemented if "abstract_property" not in method ]) >= 1
184+
185+ def test_get_unimplemented_abstract_methods_with_staticmethod_classmethod (self ):
186+ """Test with abstract static and class methods."""
187+
188+ class AbstractWithMethods (ABC ):
189+ @staticmethod
190+ @abstractmethod
191+ def abstract_static () -> str :
192+ pass
193+
194+ @classmethod
195+ @abstractmethod
196+ def abstract_class (cls ) -> str :
197+ pass
198+
199+ @abstractmethod
200+ def abstract_instance (self ) -> None :
201+ pass
202+
203+ class PartialMethodImpl (AbstractWithMethods ):
204+ @staticmethod
205+ def abstract_static () -> str :
206+ return "static implemented"
207+
208+ # abstract_class and abstract_instance not implemented
209+
210+ unimplemented = get_unimplemented_abstract_methods (PartialMethodImpl )
211+ assert unimplemented == {"abstract_class" , "abstract_instance" }
212+
213+ def test_get_unimplemented_abstract_methods_real_world_example (self ):
214+ """Test with a real-world like example similar to GracefulShutdownHandler."""
215+
216+ from py_spring_core .core .interfaces .graceful_shutdown_handler import GracefulShutdownHandler
217+
218+ class IncompleteShutdownHandler (GracefulShutdownHandler ):
219+ def on_shutdown (self , shutdown_type ) -> None :
220+ pass
221+ # on_timeout and on_error not implemented
222+
223+ unimplemented = get_unimplemented_abstract_methods (IncompleteShutdownHandler )
224+ assert "on_timeout" in unimplemented
225+ assert "on_error" in unimplemented
226+ assert "on_shutdown" not in unimplemented
0 commit comments