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.
Files changed (130) hide show
  1. {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/METADATA +2 -1
  2. {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/RECORD +21 -129
  3. airbyte_ops_mcp/cli/cloud.py +31 -2
  4. airbyte_ops_mcp/cloud_admin/api_client.py +506 -33
  5. airbyte_ops_mcp/cloud_admin/models.py +56 -0
  6. airbyte_ops_mcp/constants.py +58 -0
  7. airbyte_ops_mcp/{_legacy/airbyte_ci/metadata_service/docker_hub.py → docker_hub.py} +16 -10
  8. airbyte_ops_mcp/mcp/cloud_connector_versions.py +491 -10
  9. airbyte_ops_mcp/mcp/prerelease.py +5 -44
  10. airbyte_ops_mcp/mcp/prod_db_queries.py +128 -4
  11. airbyte_ops_mcp/mcp/regression_tests.py +10 -5
  12. airbyte_ops_mcp/{_legacy/airbyte_ci/metadata_service/validators/metadata_validator.py → metadata_validator.py} +18 -12
  13. airbyte_ops_mcp/prod_db_access/queries.py +51 -0
  14. airbyte_ops_mcp/prod_db_access/sql.py +76 -0
  15. airbyte_ops_mcp/regression_tests/ci_output.py +8 -4
  16. airbyte_ops_mcp/regression_tests/connection_fetcher.py +16 -5
  17. airbyte_ops_mcp/regression_tests/http_metrics.py +21 -2
  18. airbyte_ops_mcp/regression_tests/models.py +7 -1
  19. airbyte_ops_mcp/telemetry.py +162 -0
  20. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/.gitignore +0 -1
  21. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/README.md +0 -420
  22. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/__init__.py +0 -2
  23. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/__init__.py +0 -1
  24. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/__init__.py +0 -8
  25. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/base_backend.py +0 -16
  26. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/duckdb_backend.py +0 -87
  27. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/file_backend.py +0 -165
  28. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connection_objects_retrieval.py +0 -377
  29. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connector_runner.py +0 -247
  30. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/errors.py +0 -7
  31. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/evaluation_modes.py +0 -25
  32. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/hacks.py +0 -23
  33. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/json_schema_helper.py +0 -384
  34. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/mitm_addons.py +0 -37
  35. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/models.py +0 -595
  36. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/proxy.py +0 -207
  37. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/secret_access.py +0 -47
  38. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/segment_tracking.py +0 -45
  39. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/utils.py +0 -214
  40. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/conftest.py.disabled +0 -751
  41. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/consts.py +0 -4
  42. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/poetry.lock +0 -4480
  43. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/pytest.ini +0 -9
  44. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/__init__.py +0 -1
  45. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_check.py +0 -61
  46. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_discover.py +0 -117
  47. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_read.py +0 -627
  48. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_spec.py +0 -43
  49. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/report.py +0 -542
  50. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/stash_keys.py +0 -38
  51. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/__init__.py +0 -0
  52. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/private_details.html.j2 +0 -305
  53. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/report.html.j2 +0 -515
  54. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/utils.py +0 -187
  55. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/__init__.py +0 -0
  56. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_check.py +0 -61
  57. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_discover.py +0 -217
  58. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_read.py +0 -177
  59. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_spec.py +0 -631
  60. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/README.md +0 -91
  61. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/bin/bundle-schemas.js +0 -48
  62. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/bin/generate-metadata-models.sh +0 -36
  63. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ActorDefinitionResourceRequirements.py +0 -54
  64. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/AirbyteInternal.py +0 -22
  65. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/AllowedHosts.py +0 -18
  66. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorBreakingChanges.py +0 -65
  67. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorBuildOptions.py +0 -15
  68. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorIPCOptions.py +0 -25
  69. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetadataDefinitionV0.json +0 -897
  70. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetadataDefinitionV0.py +0 -478
  71. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorMetrics.py +0 -24
  72. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorPackageInfo.py +0 -12
  73. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryDestinationDefinition.py +0 -407
  74. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryReleases.py +0 -406
  75. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistrySourceDefinition.py +0 -407
  76. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorRegistryV0.py +0 -413
  77. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorReleases.py +0 -98
  78. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ConnectorTestSuiteOptions.py +0 -58
  79. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/GeneratedFields.py +0 -62
  80. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/GitInfo.py +0 -31
  81. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/JobType.py +0 -23
  82. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/NormalizationDestinationDefinitionConfig.py +0 -24
  83. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RegistryOverrides.py +0 -111
  84. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ReleaseStage.py +0 -15
  85. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RemoteRegistries.py +0 -23
  86. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/ResourceRequirements.py +0 -18
  87. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/RolloutConfiguration.py +0 -29
  88. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/Secret.py +0 -34
  89. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SecretStore.py +0 -22
  90. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SourceFileInfo.py +0 -16
  91. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SuggestedStreams.py +0 -18
  92. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/SupportLevel.py +0 -15
  93. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/TestConnections.py +0 -14
  94. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/__init__.py +0 -31
  95. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/generated/airbyte-connector-metadata-schema.json +0 -0
  96. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ActorDefinitionResourceRequirements.yaml +0 -30
  97. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/AirbyteInternal.yaml +0 -32
  98. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/AllowedHosts.yaml +0 -13
  99. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorBreakingChanges.yaml +0 -65
  100. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorBuildOptions.yaml +0 -10
  101. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorIPCOptions.yaml +0 -29
  102. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorMetadataDefinitionV0.yaml +0 -172
  103. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorMetrics.yaml +0 -30
  104. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorPackageInfo.yaml +0 -9
  105. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryDestinationDefinition.yaml +0 -90
  106. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryReleases.yaml +0 -35
  107. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistrySourceDefinition.yaml +0 -92
  108. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorRegistryV0.yaml +0 -18
  109. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorReleases.yaml +0 -16
  110. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ConnectorTestSuiteOptions.yaml +0 -28
  111. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/GeneratedFields.yaml +0 -16
  112. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/GitInfo.yaml +0 -21
  113. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/JobType.yaml +0 -14
  114. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/NormalizationDestinationDefinitionConfig.yaml +0 -21
  115. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RegistryOverrides.yaml +0 -38
  116. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ReleaseStage.yaml +0 -11
  117. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RemoteRegistries.yaml +0 -25
  118. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/ResourceRequirements.yaml +0 -16
  119. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/RolloutConfiguration.yaml +0 -29
  120. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/Secret.yaml +0 -19
  121. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SecretStore.yaml +0 -16
  122. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SourceFileInfo.yaml +0 -17
  123. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SuggestedStreams.yaml +0 -13
  124. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/SupportLevel.yaml +0 -10
  125. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/models/TestConnections.yaml +0 -17
  126. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/package-lock.json +0 -62
  127. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/package.json +0 -12
  128. airbyte_ops_mcp/_legacy/airbyte_ci/metadata_models/transform.py +0 -71
  129. {airbyte_internal_ops-0.4.2.dist-info → airbyte_internal_ops-0.5.1.dist-info}/WHEEL +0 -0
  130. {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)
@@ -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
- )