Skip to content

Commit a610fa9

Browse files
committed
add docker-based tests
1 parent a954a7b commit a610fa9

File tree

2 files changed

+95
-1
lines changed

2 files changed

+95
-1
lines changed

airbyte_cdk/test/standard_tests/connector_base.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,21 @@
77
import importlib
88
import inspect
99
import os
10+
import shutil
1011
import sys
1112
from collections.abc import Callable
1213
from pathlib import Path
1314
from typing import cast
1415

16+
import pytest
1517
import yaml
1618
from boltons.typeutils import classproperty
1719

1820
from airbyte_cdk.models import (
1921
AirbyteMessage,
2022
Type,
2123
)
24+
from airbyte_cdk.models.connector_metadata import MetadataFile
2225
from airbyte_cdk.test import entrypoint_wrapper
2326
from airbyte_cdk.test.standard_tests._job_runner import IConnector, run_test_job
2427
from airbyte_cdk.test.standard_tests.models import (
@@ -28,6 +31,7 @@
2831
ACCEPTANCE_TEST_CONFIG,
2932
find_connector_root,
3033
)
34+
from airbyte_cdk.utils.docker import build_connector_image, run_docker_command
3135

3236

3337
class ConnectorTestSuiteBase(abc.ABC):
@@ -179,3 +183,73 @@ def get_scenarios(
179183
test.configured_catalog_path = connector_root / test.configured_catalog_path
180184

181185
return test_scenarios
186+
187+
@pytest.mark.skipif(
188+
shutil.which("docker") is None,
189+
reason="docker CLI not found in PATH, skipping docker image tests",
190+
)
191+
def test_docker_image_build_and_spec(
192+
self,
193+
) -> None:
194+
"""Run `docker_image` acceptance tests."""
195+
tag = "dev-latest"
196+
connector_dir = self.get_connector_root_dir()
197+
metadata = MetadataFile.from_file(connector_dir / "metadata.yaml")
198+
build_connector_image(
199+
connector_name=connector_dir.name,
200+
connector_directory=connector_dir,
201+
metadata=metadata,
202+
tag=tag,
203+
no_verify=False,
204+
)
205+
run_docker_command(
206+
[
207+
"docker",
208+
"run",
209+
"--rm",
210+
f"{metadata.data.dockerRepository}:{tag}",
211+
"spec",
212+
],
213+
)
214+
215+
@pytest.mark.skipif(
216+
shutil.which("docker") is None,
217+
reason="docker CLI not found in PATH, skipping docker image tests",
218+
)
219+
def test_docker_image_build_and_check(
220+
self,
221+
scenario: ConnectorTestScenario,
222+
) -> None:
223+
"""Run `docker_image` acceptance tests.
224+
225+
This test builds the connector image and runs the `check` command inside the container.
226+
227+
Note:
228+
- It is expected for docker image caches to be reused between test runs.
229+
- In the rare case that image caches need to be cleared, please clear
230+
the local docker image cache using `docker image prune -a` command.
231+
"""
232+
tag = "dev-latest"
233+
connector_dir = self.get_connector_root_dir()
234+
metadata = MetadataFile.from_file(connector_dir / "metadata.yaml")
235+
build_connector_image(
236+
connector_name=connector_dir.name,
237+
connector_directory=connector_dir,
238+
metadata=metadata,
239+
tag=tag,
240+
no_verify=False,
241+
)
242+
container_config_path = "/secrets/config.json"
243+
with scenario.with_temp_config_file() as temp_config_file:
244+
run_docker_command(
245+
[
246+
"docker",
247+
"run",
248+
"--rm",
249+
"-v",
250+
f"{temp_config_file}:{container_config_path}",
251+
f"{metadata.data.dockerRepository}:{tag}",
252+
"check",
253+
f"--config={container_config_path}",
254+
],
255+
)

airbyte_cdk/test/standard_tests/models/scenario.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99

1010
from __future__ import annotations
1111

12+
import json
13+
import tempfile
14+
from contextlib import contextmanager, suppress
1215
from pathlib import Path
13-
from typing import Any, Literal, cast
16+
from typing import TYPE_CHECKING, Any, Literal, cast
1417

1518
import yaml
1619
from pydantic import BaseModel
1720

21+
if TYPE_CHECKING:
22+
from collections.abc import Generator
23+
1824

1925
class ConnectorTestScenario(BaseModel):
2026
"""Acceptance test scenario, as a Pydantic model.
@@ -83,3 +89,17 @@ def __str__(self) -> str:
8389
return f"'{self.config_path.name}' Test Scenario"
8490

8591
return f"'{hash(self)}' Test Scenario"
92+
93+
@contextmanager
94+
def with_temp_config_file(self) -> Generator[Path, None, None]:
95+
"""Yield a temporary JSON file path containing the config dict and delete it on exit."""
96+
config = self.get_config_dict(empty_if_missing=True)
97+
_, path_str = tempfile.mkstemp(prefix="config-", suffix=".json", text=True)
98+
path = Path(path_str)
99+
try:
100+
path.write_text(json.dumps(config))
101+
yield path
102+
finally:
103+
# attempt cleanup, ignore errors
104+
with suppress(OSError):
105+
path.unlink()

0 commit comments

Comments
 (0)