Skip to content

Commit cc5730f

Browse files
committed
tests/unit: Add unit tests for reorg handling
- Add test_reorg_result_string_representation - Enhance test_all_loaders_implement_required_methods to check whether it has real implementation in each data loader - Remove now redundant test test_create_table_from_schema_not_just_pass
1 parent 5a23c12 commit cc5730f

File tree

2 files changed

+599
-34
lines changed

2 files changed

+599
-34
lines changed

tests/unit/test_base.py

Lines changed: 74 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
try:
1717
from src.amp.loaders.base import DataLoader, LoadConfig, LoadMode, LoadResult
18+
from src.amp.streaming.types import BlockRange
1819
except ImportError:
1920
# Skip tests if modules not available
2021
pytest.skip('amp modules not available', allow_module_level=True)
@@ -60,6 +61,28 @@ def test_failure_result_string_representation(self):
6061
assert 'Connection failed' in result_str
6162
assert 'test_table' in result_str
6263

64+
def test_reorg_result_string_representation(self):
65+
"""Test string representation of reorg LoadResult"""
66+
invalidation_ranges = [
67+
BlockRange(network='ethereum', start=100, end=110),
68+
BlockRange(network='polygon', start=200, end=205),
69+
]
70+
71+
result = LoadResult(
72+
rows_loaded=0,
73+
duration=0.5,
74+
ops_per_second=0,
75+
table_name='blocks',
76+
loader_type='postgresql',
77+
success=True,
78+
is_reorg=True,
79+
invalidation_ranges=invalidation_ranges,
80+
)
81+
82+
result_str = str(result)
83+
assert '🔄 Reorg detected' in result_str
84+
assert '2 ranges invalidated' in result_str
85+
6386

6487
@pytest.mark.unit
6588
class TestLoadConfig:
@@ -177,9 +200,54 @@ def _get_method_definitions(self, loader_class: type, method_name: str) -> List[
177200

178201
return method_defs
179202

203+
def _verify_method_implementation(self, loader_name: str, loader_class: type, method_name: str) -> None:
204+
"""Verify that a method is actually implemented, not just inherited as a stub"""
205+
method = getattr(loader_class, method_name)
206+
207+
# First try to check the source code
208+
try:
209+
source = inspect.getsource(method)
210+
211+
# Check if method is inherited from base class (not defined in this specific class)
212+
# If 'class <LoaderClassName>' is NOT in source, it means method comes from base class
213+
if f'class {loader_class}' not in source:
214+
# Method is inherited, check if it's a stub
215+
if method_name == '_handle_reorg':
216+
# For _handle_reorg, check if it just raises NotImplementedError
217+
if 'raise NotImplementedError' in source:
218+
pytest.fail(
219+
f'{loader_name} does not implement {method_name}() - it inherits the '
220+
f'NotImplementedError from base class. Each loader must implement '
221+
f'this method appropriately for its storage backend.'
222+
)
223+
elif (
224+
'pass' in source
225+
and len([line for line in source.split('\n') if line.strip() and not line.strip().startswith('#')]) <= 2
226+
):
227+
# Method is just 'pass'
228+
pytest.fail(f"{loader_name}.{method_name} is just 'pass' - needs implementation")
229+
230+
except (OSError, TypeError):
231+
# Can't get source, try runtime approach for _handle_reorg
232+
if method_name == '_handle_reorg':
233+
try:
234+
# Create a dummy instance to test the method
235+
test_instance = loader_class({'test': 'config'})
236+
test_instance._handle_reorg([], 'test_table')
237+
# If we get here, method didn't raise NotImplementedError - it's implemented
238+
except NotImplementedError:
239+
# Method raises NotImplementedError - not implemented
240+
pytest.fail(
241+
f'{loader_name} does not implement {method_name}() - it raises NotImplementedError. '
242+
f'Each loader must implement this method.'
243+
)
244+
except Exception:
245+
# Some other error occurred during execution, assume it's implemented
246+
pass
247+
180248
def test_all_loaders_implement_required_methods(self):
181-
"""Test that all loader implementations have required methods"""
182-
required_methods = ['connect', 'disconnect', '_load_batch_impl', '_create_table_from_schema']
249+
"""Test that all loader implementations properly implement required methods (not just inherit stubs)"""
250+
required_methods = ['connect', 'disconnect', '_load_batch_impl', '_create_table_from_schema', '_handle_reorg']
183251

184252
loaders = self._get_loader_classes()
185253

@@ -189,10 +257,13 @@ def test_all_loaders_implement_required_methods(self):
189257
for method_name in required_methods:
190258
assert hasattr(loader_class, method_name), f'{loader_name} missing required method: {method_name}'
191259

192-
# Check that the method is actually implemented (not just inherited abstract)
260+
# Check that the method is actually implemented (not just inherited stub)
193261
method = getattr(loader_class, method_name)
194262
assert method is not None, f'{loader_name}.{method_name} is None'
195263

264+
# Verify the method is actually implemented in this class, not just a stub
265+
self._verify_method_implementation(loader_name, loader_class, method_name)
266+
196267
def test_no_duplicate_method_definitions(self):
197268
"""Test that no loader has duplicate method definitions"""
198269
critical_methods = ['_create_table_from_schema', '_load_batch_impl', 'connect', 'disconnect']
@@ -203,34 +274,3 @@ def test_no_duplicate_method_definitions(self):
203274
for method_name in critical_methods:
204275
definitions = self._get_method_definitions(loader_class, method_name)
205276
assert len(definitions) <= 1, f'{loader_name} has duplicate {method_name} definitions at: {definitions}'
206-
207-
def test_create_table_from_schema_not_just_pass(self):
208-
"""Test that _create_table_from_schema methods have meaningful implementations"""
209-
loaders = self._get_loader_classes()
210-
211-
for loader_name, loader_class in loaders.items():
212-
method = getattr(loader_class, '_create_table_from_schema', None)
213-
if method:
214-
# Get source code
215-
try:
216-
source = inspect.getsource(method)
217-
# Check if it's just 'pass' or has actual implementation
218-
lines = [line.strip() for line in source.split('\n') if line.strip()]
219-
220-
# Filter out comments and docstrings
221-
code_lines = []
222-
for line in lines:
223-
if not line.startswith('#') and not line.startswith('"""') and not line.startswith("'''"):
224-
code_lines.append(line)
225-
226-
# Should have more than just the method definition line and 'pass'
227-
if len(code_lines) <= 2: # def line + pass line only
228-
last_line = code_lines[-1] if code_lines else ''
229-
if last_line == 'pass':
230-
pytest.fail(
231-
f"{loader_name}._create_table_from_schema is just 'pass' - needs implementation"
232-
)
233-
234-
except (OSError, TypeError):
235-
# Can't get source, skip this check
236-
pass

0 commit comments

Comments
 (0)