diff --git a/singer_sdk/testing/factory.py b/singer_sdk/testing/factory.py index f31930017..30740b6ef 100644 --- a/singer_sdk/testing/factory.py +++ b/singer_sdk/testing/factory.py @@ -8,6 +8,7 @@ from .config import SuiteConfig from .runners import TapTestRunner, TargetTestRunner from .suites import ( + TestSuite, tap_stream_attribute_tests, tap_stream_tests, tap_tests, @@ -15,7 +16,12 @@ ) if t.TYPE_CHECKING: - from singer_sdk import Tap, Target + from singer_sdk import Stream, Tap, Target + from singer_sdk.testing.templates import ( + AttributeTestTemplate, + StreamTestTemplate, + TapTestTemplate, + ) class BaseTestClass: @@ -144,8 +150,7 @@ def runner(self) -> TapTestRunner | TargetTestRunner: return TapTestClass - # TODO: Refactor this. It's too long and nested. - def _annotate_test_class( # noqa: C901 + def _annotate_test_class( self, empty_test_class: type[BaseTestClass], test_suites: list, @@ -163,80 +168,101 @@ def _annotate_test_class( # noqa: C901 """ for suite in test_suites: if suite.kind == "tap": - for test_class in suite.tests: - test = test_class() - test_name = f"test_{suite.kind}_{test.name}" - setattr(empty_test_class, test_name, test.run) + self._with_tap_tests(empty_test_class, suite) if suite.kind in {"tap_stream", "tap_stream_attribute"}: streams = list(test_runner.new_tap().streams.values()) if suite.kind == "tap_stream": - params = [ + self._with_stream_tests(empty_test_class, suite, streams) + + if suite.kind == "tap_stream_attribute": + self._with_stream_attribute_tests(empty_test_class, suite, streams) + + return empty_test_class + + def _with_tap_tests( + self, + empty_test_class: type[BaseTestClass], + suite: TestSuite[TapTestTemplate], + ) -> None: + for test_class in suite.tests: + test = test_class() + test_name = f"test_{suite.kind}_{test.name}" + setattr(empty_test_class, test_name, test.run) + + def _with_stream_tests( + self, + empty_test_class: type[BaseTestClass], + suite: TestSuite[StreamTestTemplate], + streams: list[Stream], + ) -> None: + params = [ + { + "stream": stream, + } + for stream in streams + ] + param_ids = [stream.name for stream in streams] + + for test_class in suite.tests: + test = test_class() + test_name = f"test_{suite.kind}_{test.name}" + setattr( + empty_test_class, + test_name, + test.run, + ) + empty_test_class.params[test_name] = params + empty_test_class.param_ids[test_name] = param_ids + + def _with_stream_attribute_tests( + self, + empty_test_class: type[BaseTestClass], + suite: TestSuite[AttributeTestTemplate], + streams: list[Stream], + ) -> None: + for test_class in suite.tests: + test = test_class() + test_name = f"test_{suite.kind}_{test.name}" + test_params = [] + test_ids: list[str] = [] + for stream in streams: + final_schema = stream.stream_maps[-1].transformed_schema["properties"] + test_params.extend( + [ { "stream": stream, + "attribute_name": prop_name, } - for stream in streams - ] - param_ids = [stream.name for stream in streams] - - for test_class in suite.tests: - test = test_class() - test_name = f"test_{suite.kind}_{test.name}" - setattr( - empty_test_class, - test_name, - test.run, + for prop_name, prop_schema in final_schema.items() + if test_class.evaluate( + stream=stream, + property_name=prop_name, + property_schema=prop_schema, ) - empty_test_class.params[test_name] = params - empty_test_class.param_ids[test_name] = param_ids - - if suite.kind == "tap_stream_attribute": - for test_class in suite.tests: - test = test_class() - test_name = f"test_{suite.kind}_{test.name}" - test_params = [] - test_ids: list[str] = [] - for stream in streams: - final_schema = stream.stream_maps[-1].transformed_schema[ - "properties" - ] - test_params.extend( - [ - { - "stream": stream, - "attribute_name": prop_name, - } - for prop_name, prop_schema in final_schema.items() - if test_class.evaluate( - stream=stream, - property_name=prop_name, - property_schema=prop_schema, - ) - ], - ) - test_ids.extend( - [ - f"{stream.name}.{prop_name}" - for prop_name, prop_schema in final_schema.items() - if test_class.evaluate( - stream=stream, - property_name=prop_name, - property_schema=prop_schema, - ) - ], - ) - - if test_params: - setattr( - empty_test_class, - test_name, - test.run, - ) - empty_test_class.params[test_name] = test_params - empty_test_class.param_ids[test_name] = test_ids + ], + ) + test_ids.extend( + [ + f"{stream.name}.{prop_name}" + for prop_name, prop_schema in final_schema.items() + if test_class.evaluate( + stream=stream, + property_name=prop_name, + property_schema=prop_schema, + ) + ], + ) - return empty_test_class + if test_params: + setattr( + empty_test_class, + test_name, + test.run, + ) + empty_test_class.params[test_name] = test_params + empty_test_class.param_ids[test_name] = test_ids class TargetTestClassFactory: diff --git a/singer_sdk/testing/suites.py b/singer_sdk/testing/suites.py index d795cf153..df93c86d2 100644 --- a/singer_sdk/testing/suites.py +++ b/singer_sdk/testing/suites.py @@ -42,17 +42,17 @@ TargetSchemaUpdates, TargetSpecialCharsInAttributes, ) +from .templates import TestTemplate -if t.TYPE_CHECKING: - from .templates import TapTestTemplate, TargetTestTemplate, TestTemplate +T = t.TypeVar("T", bound=TestTemplate) @dataclass -class TestSuite: +class TestSuite(t.Generic[T]): """Test Suite container class.""" kind: str - tests: list[type[TestTemplate] | type[TapTestTemplate] | type[TargetTestTemplate]] + tests: list[type[T]] # Tap Test Suites