airbyte-internal-ops 0.4.2__py3-none-any.whl → 0.5.1__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.
- {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/METADATA +2 -1
- {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/RECORD +21 -129
- airbyte_ops_mcp/cli/cloud.py +31 -2
- airbyte_ops_mcp/cloud_admin/api_client.py +506 -33
- airbyte_ops_mcp/cloud_admin/models.py +56 -0
- airbyte_ops_mcp/constants.py +58 -0
- airbyte_ops_mcp/{_legacy/airbyte_ci/metadata_service/docker_hub.py → docker_hub.py} +16 -10
- airbyte_ops_mcp/mcp/cloud_connector_versions.py +491 -10
- airbyte_ops_mcp/mcp/prerelease.py +5 -44
- airbyte_ops_mcp/mcp/prod_db_queries.py +128 -4
- airbyte_ops_mcp/mcp/regression_tests.py +10 -5
- airbyte_ops_mcp/{_legacy/airbyte_ci/metadata_service/validators/metadata_validator.py → metadata_validator.py} +18 -12
- airbyte_ops_mcp/prod_db_access/queries.py +51 -0
- airbyte_ops_mcp/prod_db_access/sql.py +76 -0
- airbyte_ops_mcp/regression_tests/ci_output.py +8 -4
- airbyte_ops_mcp/regression_tests/connection_fetcher.py +16 -5
- airbyte_ops_mcp/regression_tests/http_metrics.py +21 -2
- airbyte_ops_mcp/regression_tests/models.py +7 -1
- airbyte_ops_mcp/telemetry.py +162 -0
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/.gitignore +0 -1
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/README.md +0 -420
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/__init__.py +0 -2
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/__init__.py +0 -1
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/__init__.py +0 -8
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/base_backend.py +0 -16
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/duckdb_backend.py +0 -87
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/file_backend.py +0 -165
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connection_objects_retrieval.py +0 -377
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connector_runner.py +0 -247
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/errors.py +0 -7
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/evaluation_modes.py +0 -25
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/hacks.py +0 -23
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/json_schema_helper.py +0 -384
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/mitm_addons.py +0 -37
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/models.py +0 -595
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/proxy.py +0 -207
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/secret_access.py +0 -47
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/segment_tracking.py +0 -45
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/utils.py +0 -214
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/conftest.py.disabled +0 -751
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/consts.py +0 -4
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/poetry.lock +0 -4480
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/pytest.ini +0 -9
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/__init__.py +0 -1
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_check.py +0 -61
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_discover.py +0 -117
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_read.py +0 -627
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_spec.py +0 -43
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/report.py +0 -542
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/stash_keys.py +0 -38
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/__init__.py +0 -0
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/private_details.html.j2 +0 -305
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/report.html.j2 +0 -515
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/utils.py +0 -187
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/__init__.py +0 -0
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_check.py +0 -61
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_discover.py +0 -217
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_read.py +0 -177
- airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_spec.py +0 -631
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/README.md +0 -91
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/bin/bundle-schemas.js +0 -48
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/bin/generate-metadata-models.sh +0 -36
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ActorDefinitionResourceRequirements.py +0 -54
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/AirbyteInternal.py +0 -22
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/AllowedHosts.py +0 -18
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorBreakingChanges.py +0 -65
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorBuildOptions.py +0 -15
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorIPCOptions.py +0 -25
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetadataDefinitionV0.json +0 -897
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetadataDefinitionV0.py +0 -478
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetrics.py +0 -24
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorPackageInfo.py +0 -12
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryDestinationDefinition.py +0 -407
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryReleases.py +0 -406
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistrySourceDefinition.py +0 -407
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryV0.py +0 -413
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorReleases.py +0 -98
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorTestSuiteOptions.py +0 -58
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/GeneratedFields.py +0 -62
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/GitInfo.py +0 -31
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/JobType.py +0 -23
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/NormalizationDestinationDefinitionConfig.py +0 -24
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RegistryOverrides.py +0 -111
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ReleaseStage.py +0 -15
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RemoteRegistries.py +0 -23
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ResourceRequirements.py +0 -18
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RolloutConfiguration.py +0 -29
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/Secret.py +0 -34
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SecretStore.py +0 -22
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SourceFileInfo.py +0 -16
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SuggestedStreams.py +0 -18
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SupportLevel.py +0 -15
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/TestConnections.py +0 -14
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/__init__.py +0 -31
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/airbyte-connector-metadata-schema.json +0 -0
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ActorDefinitionResourceRequirements.yaml +0 -30
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/AirbyteInternal.yaml +0 -32
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/AllowedHosts.yaml +0 -13
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorBreakingChanges.yaml +0 -65
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorBuildOptions.yaml +0 -10
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorIPCOptions.yaml +0 -29
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorMetadataDefinitionV0.yaml +0 -172
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorMetrics.yaml +0 -30
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorPackageInfo.yaml +0 -9
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryDestinationDefinition.yaml +0 -90
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryReleases.yaml +0 -35
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistrySourceDefinition.yaml +0 -92
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryV0.yaml +0 -18
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorReleases.yaml +0 -16
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorTestSuiteOptions.yaml +0 -28
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/GeneratedFields.yaml +0 -16
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/GitInfo.yaml +0 -21
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/JobType.yaml +0 -14
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/NormalizationDestinationDefinitionConfig.yaml +0 -21
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RegistryOverrides.yaml +0 -38
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ReleaseStage.yaml +0 -11
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RemoteRegistries.yaml +0 -25
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ResourceRequirements.yaml +0 -16
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RolloutConfiguration.yaml +0 -29
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/Secret.yaml +0 -19
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SecretStore.yaml +0 -16
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SourceFileInfo.yaml +0 -17
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SuggestedStreams.yaml +0 -13
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SupportLevel.yaml +0 -10
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/TestConnections.yaml +0 -17
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/package-lock.json +0 -62
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/package.json +0 -12
- airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/transform.py +0 -71
- {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import json
|
|
5
|
-
import logging
|
|
6
|
-
from collections.abc import Callable, Iterable
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING, Optional, Union
|
|
9
|
-
|
|
10
|
-
import pytest
|
|
11
|
-
from airbyte_protocol.models import (
|
|
12
|
-
AirbyteCatalog,
|
|
13
|
-
AirbyteMessage,
|
|
14
|
-
ConnectorSpecification,
|
|
15
|
-
Status,
|
|
16
|
-
Type,
|
|
17
|
-
) # type: ignore
|
|
18
|
-
from deepdiff import DeepDiff # type: ignore
|
|
19
|
-
from live_tests import stash_keys
|
|
20
|
-
from live_tests.commons.models import ExecutionResult
|
|
21
|
-
from live_tests.consts import MAX_LINES_IN_REPORT
|
|
22
|
-
|
|
23
|
-
if TYPE_CHECKING:
|
|
24
|
-
from _pytest.fixtures import SubRequest
|
|
25
|
-
|
|
26
|
-
MAX_DIFF_SIZE_FOR_LOGGING = 500
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_test_logger(request: SubRequest) -> logging.Logger:
|
|
30
|
-
return logging.getLogger(request.node.name)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def filter_records(messages: Iterable[AirbyteMessage]) -> Iterable[AirbyteMessage]:
|
|
34
|
-
for message in messages:
|
|
35
|
-
if message.type is Type.RECORD:
|
|
36
|
-
yield message
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def write_string_to_test_artifact(
|
|
40
|
-
request: SubRequest, content: str, filename: str, subdir: Optional[Path] = None
|
|
41
|
-
) -> Path:
|
|
42
|
-
# StashKey (in this case TEST_ARTIFACT_DIRECTORY) defines the output class of this,
|
|
43
|
-
# so this is already a Path.
|
|
44
|
-
test_artifact_directory = request.config.stash[stash_keys.TEST_ARTIFACT_DIRECTORY]
|
|
45
|
-
if subdir:
|
|
46
|
-
test_artifact_directory = test_artifact_directory / subdir
|
|
47
|
-
test_artifact_directory.mkdir(parents=True, exist_ok=True)
|
|
48
|
-
artifact_path = test_artifact_directory / filename
|
|
49
|
-
artifact_path.write_text(content)
|
|
50
|
-
return artifact_path
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def get_and_write_diff(
|
|
54
|
-
request: SubRequest,
|
|
55
|
-
control_data: Union[list, dict],
|
|
56
|
-
target_data: Union[list, dict],
|
|
57
|
-
filepath: str,
|
|
58
|
-
ignore_order: bool,
|
|
59
|
-
exclude_paths: Optional[list[str]],
|
|
60
|
-
) -> str:
|
|
61
|
-
logger = get_test_logger(request)
|
|
62
|
-
diff = DeepDiff(
|
|
63
|
-
control_data,
|
|
64
|
-
target_data,
|
|
65
|
-
ignore_order=ignore_order,
|
|
66
|
-
report_repetition=True,
|
|
67
|
-
exclude_regex_paths=exclude_paths,
|
|
68
|
-
)
|
|
69
|
-
if diff:
|
|
70
|
-
diff_json = diff.to_json()
|
|
71
|
-
parsed_diff = json.loads(diff_json)
|
|
72
|
-
formatted_diff_json = json.dumps(parsed_diff, indent=2)
|
|
73
|
-
|
|
74
|
-
diff_path_tree = write_string_to_test_artifact(
|
|
75
|
-
request, str(diff.tree), f"{filepath}_tree.txt", subdir=request.node.name
|
|
76
|
-
)
|
|
77
|
-
diff_path_text = write_string_to_test_artifact(
|
|
78
|
-
request,
|
|
79
|
-
formatted_diff_json,
|
|
80
|
-
f"{filepath}_text.txt",
|
|
81
|
-
subdir=Path(request.node.name),
|
|
82
|
-
)
|
|
83
|
-
diff_path_pretty = write_string_to_test_artifact(
|
|
84
|
-
request,
|
|
85
|
-
str(diff.pretty()),
|
|
86
|
-
f"{filepath}_pretty.txt",
|
|
87
|
-
subdir=Path(request.node.name),
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
logger.info(
|
|
91
|
-
f"Diff file are stored in {diff_path_tree}, {diff_path_text}, and {diff_path_pretty}."
|
|
92
|
-
)
|
|
93
|
-
if len(diff_json.encode("utf-8")) < MAX_DIFF_SIZE_FOR_LOGGING:
|
|
94
|
-
logger.error(formatted_diff_json)
|
|
95
|
-
|
|
96
|
-
return formatted_diff_json
|
|
97
|
-
return ""
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def fail_test_on_failing_execution_results(
|
|
101
|
-
record_property: Callable, execution_results: list[ExecutionResult]
|
|
102
|
-
) -> None:
|
|
103
|
-
error_messages = []
|
|
104
|
-
for execution_result in execution_results:
|
|
105
|
-
if not execution_result.success:
|
|
106
|
-
property_suffix = f"of failing execution {execution_result.command.value} on {execution_result.connector_under_test.name}:{execution_result.connector_under_test.version} [{MAX_LINES_IN_REPORT} last lines]"
|
|
107
|
-
record_property(
|
|
108
|
-
f"Stdout {property_suffix}",
|
|
109
|
-
tail_file(execution_result.stdout_file_path, n=MAX_LINES_IN_REPORT),
|
|
110
|
-
)
|
|
111
|
-
record_property(
|
|
112
|
-
f"Stderr of {property_suffix}",
|
|
113
|
-
tail_file(execution_result.stderr_file_path, n=MAX_LINES_IN_REPORT),
|
|
114
|
-
)
|
|
115
|
-
error_messages.append(
|
|
116
|
-
f"Failed executing command {execution_result.command} on {execution_result.connector_under_test.name}:{execution_result.connector_under_test.version}"
|
|
117
|
-
)
|
|
118
|
-
if error_messages:
|
|
119
|
-
pytest.fail("\n".join(error_messages))
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def tail_file(file_path: Path, n: int = MAX_LINES_IN_REPORT) -> list[str]:
|
|
123
|
-
with open(file_path) as f:
|
|
124
|
-
# Move the cursor to the end of the file
|
|
125
|
-
f.seek(0, 2)
|
|
126
|
-
file_size = f.tell()
|
|
127
|
-
lines: list[str] = []
|
|
128
|
-
read_size = min(4096, file_size)
|
|
129
|
-
cursor = file_size - read_size
|
|
130
|
-
|
|
131
|
-
# Read chunks of the file until we've found n lines
|
|
132
|
-
while len(lines) < n and cursor >= 0:
|
|
133
|
-
f.seek(cursor)
|
|
134
|
-
chunk = f.read(read_size)
|
|
135
|
-
lines.extend(chunk.splitlines(True)[-n:])
|
|
136
|
-
cursor -= read_size
|
|
137
|
-
|
|
138
|
-
# Return the last n lines
|
|
139
|
-
return lines[-n:]
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
def is_successful_check(execution_result: ExecutionResult) -> bool:
|
|
143
|
-
for message in execution_result.airbyte_messages:
|
|
144
|
-
if (
|
|
145
|
-
message.type is Type.CONNECTION_STATUS
|
|
146
|
-
and message.connectionStatus
|
|
147
|
-
and message.connectionStatus.status is Status.SUCCEEDED
|
|
148
|
-
):
|
|
149
|
-
return True
|
|
150
|
-
return False
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def get_catalog(execution_result: ExecutionResult) -> AirbyteCatalog:
|
|
154
|
-
catalog = [
|
|
155
|
-
m.catalog
|
|
156
|
-
for m in execution_result.airbyte_messages
|
|
157
|
-
if m.type is Type.CATALOG and m.catalog
|
|
158
|
-
]
|
|
159
|
-
try:
|
|
160
|
-
return catalog[0]
|
|
161
|
-
except ValueError:
|
|
162
|
-
raise ValueError(
|
|
163
|
-
f"Expected exactly one catalog in the execution result, but got {len(catalog)}."
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def get_spec(execution_result: ExecutionResult) -> ConnectorSpecification:
|
|
168
|
-
spec = [m.spec for m in execution_result.airbyte_messages if m.type is Type.SPEC]
|
|
169
|
-
try:
|
|
170
|
-
return spec[0]
|
|
171
|
-
except ValueError:
|
|
172
|
-
raise ValueError(
|
|
173
|
-
f"Expected exactly one spec in the execution result, but got {len(spec)}."
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def find_all_values_for_key_in_schema(schema: dict, searched_key: str):
|
|
178
|
-
"""Retrieve all (nested) values in a schema for a specific searched key"""
|
|
179
|
-
if isinstance(schema, list):
|
|
180
|
-
for schema_item in schema:
|
|
181
|
-
yield from find_all_values_for_key_in_schema(schema_item, searched_key)
|
|
182
|
-
if isinstance(schema, dict):
|
|
183
|
-
for key, value in schema.items():
|
|
184
|
-
if key == searched_key:
|
|
185
|
-
yield value
|
|
186
|
-
if isinstance(value, dict) or isinstance(value, list):
|
|
187
|
-
yield from find_all_values_for_key_in_schema(value, searched_key)
|
|
File without changes
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
-
#
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
from typing import Callable
|
|
7
|
-
|
|
8
|
-
import pytest
|
|
9
|
-
from airbyte_protocol.models import Type
|
|
10
|
-
from live_tests.commons.models import ExecutionResult
|
|
11
|
-
from live_tests.consts import MAX_LINES_IN_REPORT
|
|
12
|
-
from live_tests.utils import (
|
|
13
|
-
fail_test_on_failing_execution_results,
|
|
14
|
-
is_successful_check,
|
|
15
|
-
tail_file,
|
|
16
|
-
)
|
|
17
|
-
|
|
18
|
-
pytestmark = [
|
|
19
|
-
pytest.mark.anyio,
|
|
20
|
-
]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@pytest.mark.allow_diagnostic_mode
|
|
24
|
-
async def test_check_succeeds(
|
|
25
|
-
record_property: Callable,
|
|
26
|
-
check_target_execution_result: ExecutionResult,
|
|
27
|
-
) -> None:
|
|
28
|
-
"""
|
|
29
|
-
Verify that the check command succeeds on the target connection.
|
|
30
|
-
|
|
31
|
-
Success is determined by the presence of a connection status message with a status of SUCCEEDED.
|
|
32
|
-
"""
|
|
33
|
-
fail_test_on_failing_execution_results(
|
|
34
|
-
record_property,
|
|
35
|
-
[check_target_execution_result],
|
|
36
|
-
)
|
|
37
|
-
assert (
|
|
38
|
-
len(
|
|
39
|
-
[
|
|
40
|
-
msg
|
|
41
|
-
for msg in check_target_execution_result.airbyte_messages
|
|
42
|
-
if msg.type == Type.CONNECTION_STATUS
|
|
43
|
-
]
|
|
44
|
-
)
|
|
45
|
-
== 1
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
successful_target_check: bool = is_successful_check(check_target_execution_result)
|
|
49
|
-
error_messages = []
|
|
50
|
-
if not successful_target_check:
|
|
51
|
-
record_property(
|
|
52
|
-
f"Target CHECK standard output [Last {MAX_LINES_IN_REPORT} lines]",
|
|
53
|
-
tail_file(
|
|
54
|
-
check_target_execution_result.stdout_file_path, n=MAX_LINES_IN_REPORT
|
|
55
|
-
),
|
|
56
|
-
)
|
|
57
|
-
error_messages.append(
|
|
58
|
-
"The target check did not succeed. Check the test artifacts for more information."
|
|
59
|
-
)
|
|
60
|
-
if error_messages:
|
|
61
|
-
pytest.fail("\n".join(error_messages))
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
-
#
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
from typing import Callable, List, Union
|
|
7
|
-
|
|
8
|
-
import dpath.util
|
|
9
|
-
import jsonschema
|
|
10
|
-
import pytest
|
|
11
|
-
from airbyte_protocol.models import AirbyteCatalog
|
|
12
|
-
from live_tests.commons.models import ExecutionResult
|
|
13
|
-
from live_tests.utils import (
|
|
14
|
-
fail_test_on_failing_execution_results,
|
|
15
|
-
find_all_values_for_key_in_schema,
|
|
16
|
-
get_catalog,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
pytestmark = [
|
|
20
|
-
pytest.mark.anyio,
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
@pytest.fixture(scope="session")
|
|
25
|
-
def target_discovered_catalog(
|
|
26
|
-
discover_target_execution_result: ExecutionResult,
|
|
27
|
-
) -> AirbyteCatalog:
|
|
28
|
-
return get_catalog(discover_target_execution_result)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@pytest.mark.allow_diagnostic_mode
|
|
32
|
-
async def test_discover(
|
|
33
|
-
record_property: Callable,
|
|
34
|
-
discover_target_execution_result: ExecutionResult,
|
|
35
|
-
target_discovered_catalog: AirbyteCatalog,
|
|
36
|
-
):
|
|
37
|
-
"""
|
|
38
|
-
Verify that the discover command succeeds on the target connection.
|
|
39
|
-
|
|
40
|
-
Success is determined by the presence of a catalog with one or more streams, all with unique names.
|
|
41
|
-
"""
|
|
42
|
-
fail_test_on_failing_execution_results(
|
|
43
|
-
record_property,
|
|
44
|
-
[discover_target_execution_result],
|
|
45
|
-
)
|
|
46
|
-
duplicated_stream_names = _duplicated_stream_names(
|
|
47
|
-
target_discovered_catalog.streams
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
assert target_discovered_catalog is not None, "Message should have catalog"
|
|
51
|
-
assert (
|
|
52
|
-
hasattr(target_discovered_catalog, "streams")
|
|
53
|
-
and target_discovered_catalog.streams
|
|
54
|
-
), "Catalog should contain streams"
|
|
55
|
-
assert len(duplicated_stream_names) == 0, (
|
|
56
|
-
f"Catalog should have uniquely named streams, duplicates are: {duplicated_stream_names}"
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _duplicated_stream_names(streams) -> List[str]:
|
|
61
|
-
"""Counts number of times a stream appears in the catalog"""
|
|
62
|
-
name_counts = dict()
|
|
63
|
-
for stream in streams:
|
|
64
|
-
count = name_counts.get(stream.name, 0)
|
|
65
|
-
name_counts[stream.name] = count + 1
|
|
66
|
-
return [k for k, v in name_counts.items() if v > 1]
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
@pytest.mark.allow_diagnostic_mode
|
|
70
|
-
async def test_streams_have_valid_json_schemas(
|
|
71
|
-
target_discovered_catalog: AirbyteCatalog,
|
|
72
|
-
):
|
|
73
|
-
"""Check if all stream schemas are valid json schemas."""
|
|
74
|
-
for stream in target_discovered_catalog.streams:
|
|
75
|
-
jsonschema.Draft7Validator.check_schema(stream.json_schema)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
@pytest.mark.allow_diagnostic_mode
|
|
79
|
-
async def test_defined_cursors_exist_in_schema(
|
|
80
|
-
target_discovered_catalog: AirbyteCatalog,
|
|
81
|
-
):
|
|
82
|
-
"""Check if all of the source defined cursor fields exist on stream's json schema."""
|
|
83
|
-
for stream in target_discovered_catalog.streams:
|
|
84
|
-
if not stream.default_cursor_field:
|
|
85
|
-
continue
|
|
86
|
-
schema = stream.json_schema
|
|
87
|
-
assert "properties" in schema, (
|
|
88
|
-
f"Top level item should have an 'object' type for {stream.name} stream schema"
|
|
89
|
-
)
|
|
90
|
-
cursor_path = "/properties/".join(stream.default_cursor_field)
|
|
91
|
-
cursor_field_location = dpath.util.search(schema["properties"], cursor_path)
|
|
92
|
-
assert cursor_field_location, (
|
|
93
|
-
f"Some of defined cursor fields {stream.default_cursor_field} are not specified in discover schema "
|
|
94
|
-
f"properties for {stream.name} stream"
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
@pytest.mark.allow_diagnostic_mode
|
|
99
|
-
async def test_defined_refs_exist_in_schema(target_discovered_catalog: AirbyteCatalog):
|
|
100
|
-
"""Check the presence of unresolved `$ref`s values within each json schema."""
|
|
101
|
-
schemas_errors = []
|
|
102
|
-
for stream in target_discovered_catalog.streams:
|
|
103
|
-
check_result = list(
|
|
104
|
-
find_all_values_for_key_in_schema(stream.json_schema, "$ref")
|
|
105
|
-
)
|
|
106
|
-
if check_result:
|
|
107
|
-
schemas_errors.append({stream.name: check_result})
|
|
108
|
-
|
|
109
|
-
assert not schemas_errors, (
|
|
110
|
-
f"Found unresolved `$refs` values for selected streams: {tuple(schemas_errors)}."
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
@pytest.mark.allow_diagnostic_mode
|
|
115
|
-
@pytest.mark.parametrize("keyword", ["allOf", "not"])
|
|
116
|
-
async def test_defined_keyword_exist_in_schema(
|
|
117
|
-
keyword, target_discovered_catalog: AirbyteCatalog
|
|
118
|
-
):
|
|
119
|
-
"""Check for the presence of not allowed keywords within each json schema"""
|
|
120
|
-
schemas_errors = []
|
|
121
|
-
for stream in target_discovered_catalog.streams:
|
|
122
|
-
check_result = _find_keyword_schema(stream.json_schema, key=keyword)
|
|
123
|
-
if check_result:
|
|
124
|
-
schemas_errors.append(stream.name)
|
|
125
|
-
|
|
126
|
-
assert not schemas_errors, (
|
|
127
|
-
f"Found not allowed `{keyword}` keyword for selected streams: {schemas_errors}."
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def _find_keyword_schema(schema: Union[dict, list, str], key: str) -> bool:
|
|
132
|
-
"""Find at least one keyword in a schema, skip object properties"""
|
|
133
|
-
|
|
134
|
-
def _find_keyword(schema, key, _skip=False):
|
|
135
|
-
if isinstance(schema, list):
|
|
136
|
-
for v in schema:
|
|
137
|
-
_find_keyword(v, key)
|
|
138
|
-
elif isinstance(schema, dict):
|
|
139
|
-
for k, v in schema.items():
|
|
140
|
-
if k == key and not _skip:
|
|
141
|
-
raise StopIteration
|
|
142
|
-
rec_skip = k == "properties" and schema.get("type") == "object"
|
|
143
|
-
_find_keyword(v, key, rec_skip)
|
|
144
|
-
|
|
145
|
-
try:
|
|
146
|
-
_find_keyword(schema, key)
|
|
147
|
-
except StopIteration:
|
|
148
|
-
return True
|
|
149
|
-
return False
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
@pytest.mark.allow_diagnostic_mode
|
|
153
|
-
async def test_primary_keys_exist_in_schema(target_discovered_catalog: AirbyteCatalog):
|
|
154
|
-
"""Check that all primary keys are present in catalog."""
|
|
155
|
-
for stream in target_discovered_catalog.streams:
|
|
156
|
-
for pk in stream.source_defined_primary_key or []:
|
|
157
|
-
schema = stream.json_schema
|
|
158
|
-
pk_path = "/properties/".join(pk)
|
|
159
|
-
pk_field_location = dpath.util.search(schema["properties"], pk_path)
|
|
160
|
-
assert pk_field_location, (
|
|
161
|
-
f"One of the PKs ({pk}) is not specified in discover schema for {stream.name} stream"
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
@pytest.mark.allow_diagnostic_mode
|
|
166
|
-
async def test_streams_has_sync_modes(target_discovered_catalog: AirbyteCatalog):
|
|
167
|
-
"""Check that the supported_sync_modes is a not empty field in streams of the catalog."""
|
|
168
|
-
for stream in target_discovered_catalog.streams:
|
|
169
|
-
assert stream.supported_sync_modes is not None, (
|
|
170
|
-
f"The stream {stream.name} is missing supported_sync_modes field declaration."
|
|
171
|
-
)
|
|
172
|
-
assert len(stream.supported_sync_modes) > 0, (
|
|
173
|
-
f"supported_sync_modes list on stream {stream.name} should not be empty."
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
@pytest.mark.allow_diagnostic_mode
|
|
178
|
-
async def test_additional_properties_is_true(target_discovered_catalog: AirbyteCatalog):
|
|
179
|
-
"""
|
|
180
|
-
Check that value of the "additionalProperties" field is always true.
|
|
181
|
-
|
|
182
|
-
A stream schema declaring "additionalProperties": false introduces the risk of accidental breaking changes.
|
|
183
|
-
Specifically, when removing a property from the stream schema, existing connector catalog will no longer be valid.
|
|
184
|
-
False value introduces the risk of accidental breaking changes.
|
|
185
|
-
|
|
186
|
-
Read https://github.com/airbytehq/airbyte/issues/14196 for more details.
|
|
187
|
-
"""
|
|
188
|
-
for stream in target_discovered_catalog.streams:
|
|
189
|
-
additional_properties_values = list(
|
|
190
|
-
find_all_values_for_key_in_schema(
|
|
191
|
-
stream.json_schema, "additionalProperties"
|
|
192
|
-
)
|
|
193
|
-
)
|
|
194
|
-
if additional_properties_values:
|
|
195
|
-
assert all(
|
|
196
|
-
[
|
|
197
|
-
additional_properties_value is True
|
|
198
|
-
for additional_properties_value in additional_properties_values
|
|
199
|
-
]
|
|
200
|
-
), (
|
|
201
|
-
"When set, additionalProperties field value must be true for backward compatibility."
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
@pytest.mark.allow_diagnostic_mode
|
|
206
|
-
@pytest.mark.skip(
|
|
207
|
-
"This a placeholder for a CAT which has too many failures. We need to fix the connectors at scale first."
|
|
208
|
-
)
|
|
209
|
-
async def test_catalog_has_supported_data_types(
|
|
210
|
-
target_discovered_catalog: AirbyteCatalog,
|
|
211
|
-
):
|
|
212
|
-
"""
|
|
213
|
-
Check that all streams have supported data types, format and airbyte_types.
|
|
214
|
-
|
|
215
|
-
Supported data types are listed there: https://docs.airbyte.com/understanding-airbyte/supported-data-types/
|
|
216
|
-
"""
|
|
217
|
-
pass
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Copyright (c) 2023 Airbyte, Inc., all rights reserved.
|
|
3
|
-
#
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
from collections import defaultdict
|
|
7
|
-
from functools import reduce
|
|
8
|
-
from typing import TYPE_CHECKING, Any, Callable, List, Mapping, Tuple
|
|
9
|
-
|
|
10
|
-
import pytest
|
|
11
|
-
from airbyte_protocol.models import (
|
|
12
|
-
AirbyteStateMessage,
|
|
13
|
-
AirbyteStateStats,
|
|
14
|
-
AirbyteStateType,
|
|
15
|
-
AirbyteStreamStatus,
|
|
16
|
-
AirbyteStreamStatusTraceMessage,
|
|
17
|
-
ConfiguredAirbyteCatalog,
|
|
18
|
-
)
|
|
19
|
-
from live_tests.commons.json_schema_helper import conforms_to_schema
|
|
20
|
-
from live_tests.commons.models import ExecutionResult
|
|
21
|
-
from live_tests.utils import fail_test_on_failing_execution_results, get_test_logger
|
|
22
|
-
|
|
23
|
-
if TYPE_CHECKING:
|
|
24
|
-
from _pytest.fixtures import SubRequest
|
|
25
|
-
|
|
26
|
-
pytestmark = [
|
|
27
|
-
pytest.mark.anyio,
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
@pytest.mark.allow_diagnostic_mode
|
|
32
|
-
async def test_read(
|
|
33
|
-
request: SubRequest,
|
|
34
|
-
record_property: Callable,
|
|
35
|
-
read_target_execution_result: ExecutionResult,
|
|
36
|
-
):
|
|
37
|
-
"""
|
|
38
|
-
Verify that the read command succeeds on the target connection.
|
|
39
|
-
|
|
40
|
-
Also makes assertions about the validity of the read command output:
|
|
41
|
-
- At least one state message is emitted per stream
|
|
42
|
-
- Appropriate stream status messages are emitted for each stream
|
|
43
|
-
- If a primary key exists for the stream, it is present in the records emitted
|
|
44
|
-
"""
|
|
45
|
-
has_records = False
|
|
46
|
-
errors = []
|
|
47
|
-
warnings = []
|
|
48
|
-
fail_test_on_failing_execution_results(
|
|
49
|
-
record_property,
|
|
50
|
-
[read_target_execution_result],
|
|
51
|
-
)
|
|
52
|
-
for stream in read_target_execution_result.configured_catalog.streams:
|
|
53
|
-
records = read_target_execution_result.get_records_per_stream(
|
|
54
|
-
stream.stream.name
|
|
55
|
-
)
|
|
56
|
-
state_messages = read_target_execution_result.get_states_per_stream(
|
|
57
|
-
stream.stream.name
|
|
58
|
-
)
|
|
59
|
-
statuses = read_target_execution_result.get_status_messages_per_stream(
|
|
60
|
-
stream.stream.name
|
|
61
|
-
)
|
|
62
|
-
primary_key = read_target_execution_result.primary_keys_per_stream.get(
|
|
63
|
-
stream.stream.name
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
for record in records:
|
|
67
|
-
has_records = True
|
|
68
|
-
if not conforms_to_schema(
|
|
69
|
-
read_target_execution_result.get_obfuscated_types(record.record.data),
|
|
70
|
-
stream.schema(),
|
|
71
|
-
):
|
|
72
|
-
errors.append(
|
|
73
|
-
f"A record was encountered that does not conform to the schema. stream={stream.stream.name} record={record}"
|
|
74
|
-
)
|
|
75
|
-
if primary_key:
|
|
76
|
-
if _extract_primary_key_value(record.dict(), primary_key) is None:
|
|
77
|
-
errors.append(
|
|
78
|
-
f"Primary key subkeys {primary_key!r} have null values or not present in {stream.stream.name} stream records."
|
|
79
|
-
)
|
|
80
|
-
if stream.stream.name not in state_messages:
|
|
81
|
-
errors.append(
|
|
82
|
-
f"At least one state message should be emitted per stream, but no state messages were emitted for {stream.stream.name}."
|
|
83
|
-
)
|
|
84
|
-
try:
|
|
85
|
-
_validate_state_messages(
|
|
86
|
-
state_messages=state_messages[stream.stream.name],
|
|
87
|
-
configured_catalog=read_target_execution_result.configured_catalog,
|
|
88
|
-
)
|
|
89
|
-
except AssertionError as exc:
|
|
90
|
-
warnings.append(
|
|
91
|
-
f"Invalid state message for stream {stream.stream.name}. exc={exc} state_messages={state_messages[stream.stream.name]}"
|
|
92
|
-
)
|
|
93
|
-
if stream.stream.name not in statuses:
|
|
94
|
-
warnings.append(
|
|
95
|
-
f"No stream statuses were emitted for stream {stream.stream.name}."
|
|
96
|
-
)
|
|
97
|
-
if not _validate_stream_statuses(
|
|
98
|
-
configured_catalog=read_target_execution_result.configured_catalog,
|
|
99
|
-
statuses=statuses[stream.stream.name],
|
|
100
|
-
):
|
|
101
|
-
errors.append(
|
|
102
|
-
f"Invalid statuses for stream {stream.stream.name}. statuses={statuses[stream.stream.name]}"
|
|
103
|
-
)
|
|
104
|
-
if not has_records:
|
|
105
|
-
errors.append("At least one record should be read using provided catalog.")
|
|
106
|
-
|
|
107
|
-
if errors:
|
|
108
|
-
logger = get_test_logger(request)
|
|
109
|
-
for error in errors:
|
|
110
|
-
logger.info(error)
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _extract_primary_key_value(
|
|
114
|
-
record: Mapping[str, Any], primary_key: List[List[str]]
|
|
115
|
-
) -> dict[Tuple[str], Any]:
|
|
116
|
-
pk_values = {}
|
|
117
|
-
for pk_path in primary_key:
|
|
118
|
-
pk_value: Any = reduce(
|
|
119
|
-
lambda data, key: data.get(key) if isinstance(data, dict) else None,
|
|
120
|
-
pk_path,
|
|
121
|
-
record,
|
|
122
|
-
)
|
|
123
|
-
pk_values[tuple(pk_path)] = pk_value
|
|
124
|
-
return pk_values
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def _validate_stream_statuses(
|
|
128
|
-
configured_catalog: ConfiguredAirbyteCatalog,
|
|
129
|
-
statuses: List[AirbyteStreamStatusTraceMessage],
|
|
130
|
-
):
|
|
131
|
-
"""Validate all statuses for all streams in the catalogs were emitted in correct order:
|
|
132
|
-
1. STARTED
|
|
133
|
-
2. RUNNING (can be >1)
|
|
134
|
-
3. COMPLETE
|
|
135
|
-
"""
|
|
136
|
-
stream_statuses = defaultdict(list)
|
|
137
|
-
for status in statuses:
|
|
138
|
-
stream_statuses[
|
|
139
|
-
f"{status.stream_descriptor.namespace}-{status.stream_descriptor.name}"
|
|
140
|
-
].append(status.status)
|
|
141
|
-
|
|
142
|
-
assert set(
|
|
143
|
-
f"{x.stream.namespace}-{x.stream.name}" for x in configured_catalog.streams
|
|
144
|
-
) == set(stream_statuses), "All stream must emit status"
|
|
145
|
-
|
|
146
|
-
for stream_name, status_list in stream_statuses.items():
|
|
147
|
-
assert len(status_list) >= 3, (
|
|
148
|
-
f"Stream `{stream_name}` statuses should be emitted in the next order: `STARTED`, `RUNNING`,... `COMPLETE`"
|
|
149
|
-
)
|
|
150
|
-
assert status_list[0] == AirbyteStreamStatus.STARTED
|
|
151
|
-
assert status_list[-1] == AirbyteStreamStatus.COMPLETE
|
|
152
|
-
assert all(x == AirbyteStreamStatus.RUNNING for x in status_list[1:-1])
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
def _validate_state_messages(
|
|
156
|
-
state_messages: List[AirbyteStateMessage],
|
|
157
|
-
configured_catalog: ConfiguredAirbyteCatalog,
|
|
158
|
-
):
|
|
159
|
-
# Ensure that at least one state message is emitted for each stream
|
|
160
|
-
assert len(state_messages) >= len(configured_catalog.streams), (
|
|
161
|
-
"At least one state message should be emitted for each configured stream."
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
for state_message in state_messages:
|
|
165
|
-
stream_name = state_message.stream.stream_descriptor.name
|
|
166
|
-
state_type = state_message.type
|
|
167
|
-
|
|
168
|
-
# Ensure legacy state type is not emitted anymore
|
|
169
|
-
assert state_type != AirbyteStateType.LEGACY, (
|
|
170
|
-
f"Ensure that statuses from the {stream_name} stream are emitted using either "
|
|
171
|
-
"`STREAM` or `GLOBAL` state types, as the `LEGACY` state type is now deprecated."
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
# Check if stats are of the correct type and present in state message
|
|
175
|
-
assert isinstance(state_message.sourceStats, AirbyteStateStats), (
|
|
176
|
-
"Source stats should be in state message."
|
|
177
|
-
)
|