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