airbyte-cdk 6.55.0__py3-none-any.whl → 6.55.1.post11.dev15684355943__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. airbyte_cdk/cli/airbyte_cdk/_connector.py +32 -8
  2. airbyte_cdk/cli/airbyte_cdk/_image.py +76 -0
  3. airbyte_cdk/cli/airbyte_cdk/_secrets.py +13 -12
  4. airbyte_cdk/models/airbyte_protocol_serializers.py +4 -0
  5. airbyte_cdk/models/connector_metadata.py +14 -0
  6. airbyte_cdk/sources/declarative/declarative_component_schema.yaml +41 -0
  7. airbyte_cdk/sources/declarative/models/declarative_component_schema.py +28 -1
  8. airbyte_cdk/sources/declarative/parsers/model_to_component_factory.py +29 -0
  9. airbyte_cdk/sources/declarative/resolvers/__init__.py +10 -0
  10. airbyte_cdk/sources/declarative/resolvers/parametrized_components_resolver.py +125 -0
  11. airbyte_cdk/test/entrypoint_wrapper.py +163 -26
  12. airbyte_cdk/test/models/scenario.py +49 -10
  13. airbyte_cdk/test/standard_tests/__init__.py +2 -4
  14. airbyte_cdk/test/standard_tests/connector_base.py +12 -80
  15. airbyte_cdk/test/standard_tests/docker_base.py +388 -0
  16. airbyte_cdk/test/standard_tests/pytest_hooks.py +115 -2
  17. airbyte_cdk/test/standard_tests/source_base.py +13 -7
  18. airbyte_cdk/test/standard_tests/util.py +4 -3
  19. airbyte_cdk/utils/connector_paths.py +3 -3
  20. airbyte_cdk/utils/docker.py +83 -34
  21. {airbyte_cdk-6.55.0.dist-info → airbyte_cdk-6.55.1.post11.dev15684355943.dist-info}/METADATA +2 -1
  22. {airbyte_cdk-6.55.0.dist-info → airbyte_cdk-6.55.1.post11.dev15684355943.dist-info}/RECORD +26 -24
  23. {airbyte_cdk-6.55.0.dist-info → airbyte_cdk-6.55.1.post11.dev15684355943.dist-info}/LICENSE.txt +0 -0
  24. {airbyte_cdk-6.55.0.dist-info → airbyte_cdk-6.55.1.post11.dev15684355943.dist-info}/LICENSE_SHORT +0 -0
  25. {airbyte_cdk-6.55.0.dist-info → airbyte_cdk-6.55.1.post11.dev15684355943.dist-info}/WHEEL +0 -0
  26. {airbyte_cdk-6.55.0.dist-info → airbyte_cdk-6.55.1.post11.dev15684355943.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,388 @@
1
+ # Copyright (c) 2024 Airbyte, Inc., all rights reserved.
2
+ """Base class for connector test suites."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import inspect
7
+ import shutil
8
+ import sys
9
+ import tempfile
10
+ import warnings
11
+ from dataclasses import asdict
12
+ from pathlib import Path
13
+ from subprocess import CompletedProcess, SubprocessError
14
+ from typing import Literal
15
+
16
+ import orjson
17
+ import pytest
18
+ import yaml
19
+ from boltons.typeutils import classproperty
20
+
21
+ from airbyte_cdk.models import (
22
+ AirbyteCatalog,
23
+ ConfiguredAirbyteCatalog,
24
+ ConfiguredAirbyteStream,
25
+ DestinationSyncMode,
26
+ SyncMode,
27
+ )
28
+ from airbyte_cdk.models.airbyte_protocol_serializers import (
29
+ AirbyteCatalogSerializer,
30
+ AirbyteStreamSerializer,
31
+ )
32
+ from airbyte_cdk.models.connector_metadata import MetadataFile
33
+ from airbyte_cdk.test.entrypoint_wrapper import EntrypointOutput
34
+ from airbyte_cdk.test.models import ConnectorTestScenario
35
+ from airbyte_cdk.test.utils.reading import catalog
36
+ from airbyte_cdk.utils.connector_paths import (
37
+ ACCEPTANCE_TEST_CONFIG,
38
+ find_connector_root,
39
+ )
40
+ from airbyte_cdk.utils.docker import build_connector_image, run_docker_command
41
+
42
+
43
+ class DockerConnectorTestSuite:
44
+ """Base class for connector test suites."""
45
+
46
+ @classmethod
47
+ def get_test_class_dir(cls) -> Path:
48
+ """Get the file path that contains the class."""
49
+ module = sys.modules[cls.__module__]
50
+ # Get the directory containing the test file
51
+ return Path(inspect.getfile(module)).parent
52
+
53
+ @classmethod
54
+ def get_connector_root_dir(cls) -> Path:
55
+ """Get the root directory of the connector."""
56
+ return find_connector_root([cls.get_test_class_dir(), Path.cwd()])
57
+
58
+ @classproperty
59
+ def acceptance_test_config_path(cls) -> Path:
60
+ """Get the path to the acceptance test config file."""
61
+ result = cls.get_connector_root_dir() / ACCEPTANCE_TEST_CONFIG
62
+ if result.exists():
63
+ return result
64
+
65
+ raise FileNotFoundError(f"Acceptance test config file not found at: {str(result)}")
66
+
67
+ @classmethod
68
+ def get_scenarios(
69
+ cls,
70
+ ) -> list[ConnectorTestScenario]:
71
+ """Get acceptance tests for a given category.
72
+
73
+ This has to be a separate function because pytest does not allow
74
+ parametrization of fixtures with arguments from the test class itself.
75
+ """
76
+ categories = ["connection", "spec"]
77
+ try:
78
+ acceptance_test_config_path = cls.acceptance_test_config_path
79
+ except FileNotFoundError as e:
80
+ # Destinations sometimes do not have an acceptance tests file.
81
+ warnings.warn(
82
+ f"Acceptance test config file not found: {e!s}. No scenarios will be loaded.",
83
+ category=UserWarning,
84
+ stacklevel=1,
85
+ )
86
+ return []
87
+
88
+ all_tests_config = yaml.safe_load(cls.acceptance_test_config_path.read_text())
89
+ if "acceptance_tests" not in all_tests_config:
90
+ raise ValueError(
91
+ f"Acceptance tests config not found in {cls.acceptance_test_config_path}."
92
+ f" Found only: {str(all_tests_config)}."
93
+ )
94
+
95
+ test_scenarios: list[ConnectorTestScenario] = []
96
+ for category in categories:
97
+ if (
98
+ category not in all_tests_config["acceptance_tests"]
99
+ or "tests" not in all_tests_config["acceptance_tests"][category]
100
+ ):
101
+ continue
102
+
103
+ for test in all_tests_config["acceptance_tests"][category]["tests"]:
104
+ if "config_path" not in test:
105
+ # Skip tests without a config_path
106
+ continue
107
+
108
+ if "iam_role" in test["config_path"]:
109
+ # We skip iam_role tests for now, as they are not supported in the test suite.
110
+ continue
111
+
112
+ scenario = ConnectorTestScenario.model_validate(test)
113
+
114
+ if scenario.config_path and scenario.config_path in [
115
+ s.config_path for s in test_scenarios
116
+ ]:
117
+ # Skip duplicate scenarios based on config_path
118
+ continue
119
+
120
+ test_scenarios.append(scenario)
121
+
122
+ return test_scenarios
123
+
124
+ @pytest.mark.skipif(
125
+ shutil.which("docker") is None,
126
+ reason="docker CLI not found in PATH, skipping docker image tests",
127
+ )
128
+ @pytest.mark.image_tests
129
+ def test_docker_image_build_and_spec(
130
+ self,
131
+ connector_image_override: str | None,
132
+ ) -> None:
133
+ """Run `docker_image` acceptance tests."""
134
+ connector_root = self.get_connector_root_dir().absolute()
135
+ metadata = MetadataFile.from_file(connector_root / "metadata.yaml")
136
+
137
+ connector_image: str | None = connector_image_override
138
+ if not connector_image:
139
+ tag = "dev-latest"
140
+ connector_image = build_connector_image(
141
+ connector_name=connector_root.absolute().name,
142
+ connector_directory=connector_root,
143
+ metadata=metadata,
144
+ tag=tag,
145
+ no_verify=False,
146
+ )
147
+
148
+ try:
149
+ result: CompletedProcess[str] = run_docker_command(
150
+ [
151
+ "docker",
152
+ "run",
153
+ "--rm",
154
+ connector_image,
155
+ "spec",
156
+ ],
157
+ check=True, # Raise an error if the command fails
158
+ capture_stderr=True,
159
+ capture_stdout=True,
160
+ )
161
+ except SubprocessError as ex:
162
+ raise AssertionError(
163
+ f"Failed to run `spec` command in docker image {connector_image!r}. Error: {ex!s}"
164
+ ) from None
165
+
166
+ @pytest.mark.skipif(
167
+ shutil.which("docker") is None,
168
+ reason="docker CLI not found in PATH, skipping docker image tests",
169
+ )
170
+ @pytest.mark.image_tests
171
+ def test_docker_image_build_and_check(
172
+ self,
173
+ scenario: ConnectorTestScenario,
174
+ connector_image_override: str | None,
175
+ ) -> None:
176
+ """Run `docker_image` acceptance tests.
177
+
178
+ This test builds the connector image and runs the `check` command inside the container.
179
+
180
+ Note:
181
+ - It is expected for docker image caches to be reused between test runs.
182
+ - In the rare case that image caches need to be cleared, please clear
183
+ the local docker image cache using `docker image prune -a` command.
184
+ """
185
+ if scenario.expected_outcome.expect_exception():
186
+ pytest.skip("Skipping test_docker_image_build_and_check (expected to fail).")
187
+
188
+ tag = "dev-latest"
189
+ connector_root = self.get_connector_root_dir()
190
+ metadata = MetadataFile.from_file(connector_root / "metadata.yaml")
191
+ connector_image: str | None = connector_image_override
192
+ if not connector_image:
193
+ tag = "dev-latest"
194
+ connector_image = build_connector_image(
195
+ connector_name=connector_root.absolute().name,
196
+ connector_directory=connector_root,
197
+ metadata=metadata,
198
+ tag=tag,
199
+ no_verify=False,
200
+ )
201
+
202
+ container_config_path = "/secrets/config.json"
203
+ with scenario.with_temp_config_file(
204
+ connector_root=connector_root,
205
+ ) as temp_config_file:
206
+ _ = run_docker_command(
207
+ [
208
+ "docker",
209
+ "run",
210
+ "--rm",
211
+ "-v",
212
+ f"{temp_config_file}:{container_config_path}",
213
+ connector_image,
214
+ "check",
215
+ "--config",
216
+ container_config_path,
217
+ ],
218
+ check=True, # Raise an error if the command fails
219
+ capture_stderr=True,
220
+ capture_stdout=True,
221
+ )
222
+
223
+ @pytest.mark.skipif(
224
+ shutil.which("docker") is None,
225
+ reason="docker CLI not found in PATH, skipping docker image tests",
226
+ )
227
+ @pytest.mark.image_tests
228
+ def test_docker_image_build_and_read(
229
+ self,
230
+ scenario: ConnectorTestScenario,
231
+ connector_image_override: str | None,
232
+ read_from_streams: Literal["all", "none", "default"] | list[str],
233
+ read_scenarios: Literal["all", "none", "default"] | list[str],
234
+ ) -> None:
235
+ """Read from the connector's Docker image.
236
+
237
+ This test builds the connector image and runs the `read` command inside the container.
238
+
239
+ Note:
240
+ - It is expected for docker image caches to be reused between test runs.
241
+ - In the rare case that image caches need to be cleared, please clear
242
+ the local docker image cache using `docker image prune -a` command.
243
+ - If the --connector-image arg is provided, it will be used instead of building the image.
244
+ """
245
+ if scenario.expected_outcome.expect_exception():
246
+ pytest.skip("Skipping (expected to fail).")
247
+
248
+ if read_from_streams == "none":
249
+ pytest.skip("Skipping read test (`--read-from-streams=false`).")
250
+
251
+ if read_scenarios == "none":
252
+ pytest.skip("Skipping (`--read-scenarios=none`).")
253
+
254
+ default_scenario_ids = ["config", "valid_config", "default"]
255
+ if read_scenarios == "all":
256
+ pass
257
+ elif read_scenarios == "default":
258
+ if scenario.id not in default_scenario_ids:
259
+ pytest.skip(
260
+ f"Skipping read test for scenario '{scenario.id}' "
261
+ f"(not in default scenarios list '{default_scenario_ids}')."
262
+ )
263
+ elif scenario.id not in read_scenarios:
264
+ # pytest.skip(
265
+ raise ValueError(
266
+ f"Skipping read test for scenario '{scenario.id}' "
267
+ f"(not in --read-scenarios={read_scenarios})."
268
+ )
269
+
270
+ tag = "dev-latest"
271
+ connector_root = self.get_connector_root_dir()
272
+ connector_name = connector_root.absolute().name
273
+ metadata = MetadataFile.from_file(connector_root / "metadata.yaml")
274
+ connector_image: str | None = connector_image_override
275
+ if not connector_image:
276
+ tag = "dev-latest"
277
+ connector_image = build_connector_image(
278
+ connector_name=connector_name,
279
+ connector_directory=connector_root,
280
+ metadata=metadata,
281
+ tag=tag,
282
+ no_verify=False,
283
+ )
284
+
285
+ container_config_path = "/secrets/config.json"
286
+ container_catalog_path = "/secrets/catalog.json"
287
+
288
+ with (
289
+ scenario.with_temp_config_file(
290
+ connector_root=connector_root,
291
+ ) as temp_config_file,
292
+ tempfile.TemporaryDirectory(
293
+ prefix=f"{connector_name}-test",
294
+ ignore_cleanup_errors=True,
295
+ ) as temp_dir_str,
296
+ ):
297
+ temp_dir = Path(temp_dir_str)
298
+ discover_result = run_docker_command(
299
+ [
300
+ "docker",
301
+ "run",
302
+ "--rm",
303
+ "-v",
304
+ f"{temp_config_file}:{container_config_path}",
305
+ connector_image,
306
+ "discover",
307
+ "--config",
308
+ container_config_path,
309
+ ],
310
+ check=True, # Raise an error if the command fails
311
+ capture_stderr=True,
312
+ capture_stdout=True,
313
+ )
314
+ parsed_output = EntrypointOutput(messages=discover_result.stdout.splitlines())
315
+ try:
316
+ catalog_message = parsed_output.catalog # Get catalog message
317
+ assert catalog_message.catalog is not None, "Catalog message missing catalog."
318
+ discovered_catalog: AirbyteCatalog = parsed_output.catalog.catalog
319
+ except Exception as ex:
320
+ raise AssertionError(
321
+ f"Failed to load discovered catalog from {discover_result.stdout}. "
322
+ f"Error: {ex!s}"
323
+ ) from None
324
+ if not discovered_catalog.streams:
325
+ raise ValueError(
326
+ f"Discovered catalog for connector '{connector_name}' is empty. "
327
+ "Please check the connector's discover implementation."
328
+ )
329
+
330
+ streams_list = [stream.name for stream in discovered_catalog.streams]
331
+ if read_from_streams == "default" and metadata.data.suggestedStreams:
332
+ # set `streams_list` to be the intersection of discovered and suggested streams.
333
+ streams_list = list(set(streams_list) & set(metadata.data.suggestedStreams.streams))
334
+
335
+ if isinstance(read_from_streams, list):
336
+ # If `read_from_streams` is a list, we filter the discovered streams.
337
+ streams_list = list(set(streams_list) & set(read_from_streams))
338
+
339
+ configured_catalog: ConfiguredAirbyteCatalog = ConfiguredAirbyteCatalog(
340
+ streams=[
341
+ ConfiguredAirbyteStream(
342
+ stream=stream,
343
+ sync_mode=(
344
+ stream.supported_sync_modes[0]
345
+ if stream.supported_sync_modes
346
+ else SyncMode.full_refresh
347
+ ),
348
+ destination_sync_mode=DestinationSyncMode.append,
349
+ )
350
+ for stream in discovered_catalog.streams
351
+ if stream.name in streams_list
352
+ ]
353
+ )
354
+ configured_catalog_path = temp_dir / "catalog.json"
355
+ configured_catalog_path.write_text(
356
+ orjson.dumps(asdict(configured_catalog)).decode("utf-8")
357
+ )
358
+ read_result: CompletedProcess[str] = run_docker_command(
359
+ [
360
+ "docker",
361
+ "run",
362
+ "--rm",
363
+ "-v",
364
+ f"{temp_config_file}:{container_config_path}",
365
+ "-v",
366
+ f"{configured_catalog_path}:{container_catalog_path}",
367
+ connector_image,
368
+ "read",
369
+ "--config",
370
+ container_config_path,
371
+ "--catalog",
372
+ container_catalog_path,
373
+ ],
374
+ check=False,
375
+ capture_stderr=True,
376
+ capture_stdout=True,
377
+ )
378
+ if read_result.returncode != 0:
379
+ raise AssertionError(
380
+ f"Failed to run `read` command in docker image {connector_image!r}. "
381
+ "\n-----------------"
382
+ f"EXIT CODE: {read_result.returncode}\n"
383
+ "STDERR:\n"
384
+ f"{read_result.stderr}\n"
385
+ f"STDOUT:\n"
386
+ f"{read_result.stdout}\n"
387
+ "\n-----------------"
388
+ ) from None
@@ -13,12 +13,125 @@ pytest_plugins = [
13
13
  ```
14
14
  """
15
15
 
16
+ from typing import Literal, cast
17
+
16
18
  import pytest
17
19
 
18
20
 
19
- def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
21
+ @pytest.fixture
22
+ def connector_image_override(request: pytest.FixtureRequest) -> str | None:
23
+ """Return the value of --connector-image, or None if not set."""
24
+ return cast(str | None, request.config.getoption("--connector-image"))
25
+
26
+
27
+ @pytest.fixture
28
+ def read_from_streams(
29
+ request: pytest.FixtureRequest,
30
+ ) -> Literal["all", "none", "default"] | list[str]:
31
+ """Specify if the test should read from streams.
32
+
33
+ The input can be one of the following:
34
+ - [Omitted] - Default to False, meaning no streams will be read.
35
+ - `--read-from-streams`: Read from all suggested streams.
36
+ - `--read-from-streams=true`: Read from all suggested streams.
37
+ - `--read-from-streams=suggested`: Read from all suggested streams.
38
+ - `--read-from-streams=default`: Read from all suggested streams.
39
+ - `--read-from-streams=all`: Read from all streams.
40
+ - `--read-from-streams=stream1,stream2`: Read from the specified streams only.
41
+ - `--read-from-streams=false`: Do not read from any streams.
42
+ - `--read-from-streams=none`: Do not read from any streams.
43
+ """
44
+ input_val: str = request.config.getoption(
45
+ "--read-from-streams",
46
+ default="default", # type: ignore
47
+ ) # type: ignore
48
+
49
+ if isinstance(input_val, str):
50
+ if input_val.lower() == "false":
51
+ return "none"
52
+ if input_val.lower() in ["true", "suggested", "default"]:
53
+ # Default to 'default' (suggested) streams if the input is 'true', 'suggested', or
54
+ # 'default'.
55
+ # This is the default behavior if the option is not set.
56
+ return "default"
57
+ if input_val.lower() == "all":
58
+ # This will sometimes fail if the account doesn't have permissions
59
+ # to premium or restricted stream data.
60
+ return "all"
61
+
62
+ # If the input is a comma-separated list, split it into a list.
63
+ # This will return a one-element list if the input is a single stream name.
64
+ return input_val.split(",")
65
+
66
+ # Else, probably a bool; return it as is.
67
+ return input_val or "none"
68
+
69
+
70
+ @pytest.fixture
71
+ def read_scenarios(
72
+ request: pytest.FixtureRequest,
73
+ ) -> list[str] | Literal["all", "default"]:
74
+ """Return the value of `--read-scenarios`.
75
+
76
+ This argument is ignored if `--read-from-streams` is False or not set.
77
+
78
+ The input can be one of the following:
79
+ - [Omitted] - Default to 'config.json', meaning the default scenario will be read.
80
+ - `--read-scenarios=all`: Read all scenarios.
81
+ - `--read-scenarios=none`: Read no scenarios. (Overrides `--read-from-streams`, if set.)
82
+ - `--read-scenarios=scenario1,scenario2`: Read the specified scenarios only.
83
+
20
84
  """
21
- A helper for pytest_generate_tests hook.
85
+ input_val = cast(
86
+ str,
87
+ request.config.getoption(
88
+ "--read-scenarios",
89
+ default="default", # type: ignore
90
+ ),
91
+ )
92
+
93
+ if input_val.lower() == "default":
94
+ # Default config scenario is always 'config.json'.
95
+ return "default"
96
+
97
+ if input_val.lower() == "none":
98
+ # Default config scenario is always 'config.json'.
99
+ return []
100
+
101
+ return (
102
+ [
103
+ scenario_name.strip().lower().removesuffix(".json")
104
+ for scenario_name in input_val.split(",")
105
+ ]
106
+ if input_val
107
+ else []
108
+ )
109
+
110
+
111
+ def pytest_addoption(parser: pytest.Parser) -> None:
112
+ """Add --connector-image to pytest's CLI."""
113
+ parser.addoption(
114
+ "--connector-image",
115
+ action="store",
116
+ default=None,
117
+ help="Use this pre-built connector Docker image instead of building one.",
118
+ )
119
+ parser.addoption(
120
+ "--read-from-streams",
121
+ action="store",
122
+ default=None,
123
+ help=read_from_streams.__doc__,
124
+ )
125
+ parser.addoption(
126
+ "--read-scenarios",
127
+ action="store",
128
+ default="default",
129
+ help=read_scenarios.__doc__,
130
+ )
131
+
132
+
133
+ def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
134
+ """A helper for pytest_generate_tests hook.
22
135
 
23
136
  If a test method (in a class subclassed from our base class)
24
137
  declares an argument 'scenario', this function retrieves the
@@ -4,6 +4,8 @@
4
4
  from dataclasses import asdict
5
5
  from typing import TYPE_CHECKING
6
6
 
7
+ import pytest
8
+
7
9
  from airbyte_cdk.models import (
8
10
  AirbyteMessage,
9
11
  AirbyteStream,
@@ -48,13 +50,10 @@ class SourceTestSuiteBase(ConnectorTestSuiteBase):
48
50
  test_scenario=scenario,
49
51
  connector_root=self.get_connector_root_dir(),
50
52
  )
51
- conn_status_messages: list[AirbyteMessage] = [
52
- msg for msg in result._messages if msg.type == Type.CONNECTION_STATUS
53
- ] # noqa: SLF001 # Non-public API
54
- num_status_messages = len(conn_status_messages)
53
+ num_status_messages = len(result.connection_status_messages)
55
54
  assert num_status_messages == 1, (
56
55
  f"Expected exactly one CONNECTION_STATUS message. Got {num_status_messages}: \n"
57
- + "\n".join([str(m) for m in result._messages])
56
+ + "\n".join([str(m) for m in result.get_message_iterator()])
58
57
  )
59
58
 
60
59
  def test_discover(
@@ -62,6 +61,13 @@ class SourceTestSuiteBase(ConnectorTestSuiteBase):
62
61
  scenario: ConnectorTestScenario,
63
62
  ) -> None:
64
63
  """Standard test for `discover`."""
64
+ if scenario.expected_outcome.expect_exception():
65
+ # If the scenario expects an exception, we can't ensure it specifically would fail
66
+ # in discover, because some discover implementations do not need to make a connection.
67
+ # We skip this test in that case.
68
+ pytest.skip("Skipping discover test for scenario that expects an exception.")
69
+ return
70
+
65
71
  run_test_job(
66
72
  self.create_connector(scenario),
67
73
  "discover",
@@ -133,8 +139,8 @@ class SourceTestSuiteBase(ConnectorTestSuiteBase):
133
139
  catalog=configured_catalog,
134
140
  )
135
141
 
136
- if not result.records:
137
- raise AssertionError("Expected records but got none.") # noqa: TRY003
142
+ if scenario.expected_outcome.expect_success() and not result.records:
143
+ raise AssertionError("Expected records but got none.")
138
144
 
139
145
  def test_fail_read_with_bad_catalog(
140
146
  self,
@@ -10,7 +10,7 @@ from airbyte_cdk.test.standard_tests.connector_base import ConnectorTestSuiteBas
10
10
  from airbyte_cdk.test.standard_tests.declarative_sources import (
11
11
  DeclarativeSourceTestSuite,
12
12
  )
13
- from airbyte_cdk.test.standard_tests.destination_base import DestinationTestSuiteBase
13
+ from airbyte_cdk.test.standard_tests.docker_base import DockerConnectorTestSuite
14
14
  from airbyte_cdk.test.standard_tests.source_base import SourceTestSuiteBase
15
15
  from airbyte_cdk.utils.connector_paths import (
16
16
  METADATA_YAML,
@@ -18,11 +18,12 @@ from airbyte_cdk.utils.connector_paths import (
18
18
  )
19
19
 
20
20
  TEST_CLASS_MAPPING: dict[
21
- Literal["python", "manifest-only", "declarative"], type[ConnectorTestSuiteBase]
21
+ Literal["python", "manifest-only", "java"],
22
+ type[DockerConnectorTestSuite],
22
23
  ] = {
23
24
  "python": SourceTestSuiteBase,
24
25
  "manifest-only": DeclarativeSourceTestSuite,
25
- # "declarative": DeclarativeSourceTestSuite,
26
+ "java": DockerConnectorTestSuite,
26
27
  }
27
28
 
28
29
 
@@ -181,11 +181,11 @@ def find_connector_root(from_paths: list[Path]) -> Path:
181
181
  # Check if the manifest file exists in the current directory
182
182
  for parent in [path, *path.parents]:
183
183
  if (parent / METADATA_YAML).exists():
184
- return parent
184
+ return parent.absolute()
185
185
  if (parent / MANIFEST_YAML).exists():
186
- return parent
186
+ return parent.absolute()
187
187
  if (parent / ACCEPTANCE_TEST_CONFIG).exists():
188
- return parent
188
+ return parent.absolute()
189
189
  if parent.name == "airbyte_cdk":
190
190
  break
191
191