airbyte-internal-ops 0.4.1__py3-none-any.whl → 0.5.0__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 (53) hide show
  1. {airbyte_internal_ops-0.4.1.dist-info → airbyte_internal_ops-0.5.0.dist-info}/METADATA +1 -1
  2. {airbyte_internal_ops-0.4.1.dist-info → airbyte_internal_ops-0.5.0.dist-info}/RECORD +13 -52
  3. airbyte_ops_mcp/cli/cloud.py +42 -3
  4. airbyte_ops_mcp/cloud_admin/api_client.py +473 -0
  5. airbyte_ops_mcp/cloud_admin/models.py +56 -0
  6. airbyte_ops_mcp/mcp/cloud_connector_versions.py +460 -0
  7. airbyte_ops_mcp/mcp/prerelease.py +6 -46
  8. airbyte_ops_mcp/regression_tests/ci_output.py +151 -71
  9. airbyte_ops_mcp/regression_tests/http_metrics.py +21 -2
  10. airbyte_ops_mcp/regression_tests/models.py +6 -0
  11. airbyte_ops_mcp/telemetry.py +162 -0
  12. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/.gitignore +0 -1
  13. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/README.md +0 -420
  14. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/__init__.py +0 -2
  15. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/__init__.py +0 -1
  16. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/__init__.py +0 -8
  17. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/base_backend.py +0 -16
  18. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/duckdb_backend.py +0 -87
  19. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/backends/file_backend.py +0 -165
  20. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connection_objects_retrieval.py +0 -377
  21. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/connector_runner.py +0 -247
  22. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/errors.py +0 -7
  23. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/evaluation_modes.py +0 -25
  24. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/hacks.py +0 -23
  25. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/json_schema_helper.py +0 -384
  26. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/mitm_addons.py +0 -37
  27. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/models.py +0 -595
  28. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/proxy.py +0 -207
  29. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/secret_access.py +0 -47
  30. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/segment_tracking.py +0 -45
  31. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/commons/utils.py +0 -214
  32. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/conftest.py.disabled +0 -751
  33. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/consts.py +0 -4
  34. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/poetry.lock +0 -4480
  35. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/pytest.ini +0 -9
  36. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/__init__.py +0 -1
  37. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_check.py +0 -61
  38. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_discover.py +0 -117
  39. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_read.py +0 -627
  40. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/regression_tests/test_spec.py +0 -43
  41. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/report.py +0 -542
  42. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/stash_keys.py +0 -38
  43. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/__init__.py +0 -0
  44. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/private_details.html.j2 +0 -305
  45. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/templates/report.html.j2 +0 -515
  46. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/utils.py +0 -187
  47. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/__init__.py +0 -0
  48. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_check.py +0 -61
  49. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_discover.py +0 -217
  50. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_read.py +0 -177
  51. airbyte_ops_mcp/_legacy/airbyte_ci/connector_live_tests/validation_tests/test_spec.py +0 -631
  52. {airbyte_internal_ops-0.4.1.dist-info → airbyte_internal_ops-0.5.0.dist-info}/WHEEL +0 -0
  53. {airbyte_internal_ops-0.4.1.dist-info → airbyte_internal_ops-0.5.0.dist-info}/entry_points.txt +0 -0
@@ -1,627 +0,0 @@
1
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
2
- from __future__ import annotations
3
-
4
- import json
5
- from collections.abc import Callable, Generator, Iterable
6
- from typing import TYPE_CHECKING, Any, Optional
7
-
8
- import pytest
9
- from airbyte_protocol.models import AirbyteMessage # type: ignore
10
- from deepdiff import DeepDiff # type: ignore
11
- from live_tests.commons.models import ExecutionResult
12
- from live_tests.utils import (
13
- fail_test_on_failing_execution_results,
14
- get_and_write_diff,
15
- get_test_logger,
16
- write_string_to_test_artifact,
17
- )
18
-
19
- if TYPE_CHECKING:
20
- from _pytest.fixtures import SubRequest
21
-
22
- pytestmark = [
23
- pytest.mark.anyio,
24
- ]
25
-
26
-
27
- EXCLUDE_PATHS = ["emitted_at"]
28
-
29
-
30
- class TestDataIntegrity:
31
- """This class contains tests that check if the data integrity is preserved between the control and target versions.
32
- The tests have some overlap but they are meant to be gradually stricter in terms of integrity checks.
33
-
34
- 1. test_record_count: On each stream, check if the target version produces at least the same number of records as the control version.
35
- 2. test_all_pks_are_produced_in_target_version: On each stream, check if all primary key values produced by the control version are present in the target version.
36
- 3. test_all_records_are_the_same: On each stream, check if all records produced by the control version are the same as in the target version. This will write a diff of the records to the test artifacts.
37
-
38
- All these test have a full refresh and incremental variant.
39
- """
40
-
41
- async def _check_all_pks_are_produced_in_target_version(
42
- self,
43
- request: SubRequest,
44
- record_property: Callable,
45
- read_with_state_control_execution_result: ExecutionResult,
46
- read_with_state_target_execution_result: ExecutionResult,
47
- ) -> None:
48
- """This test gathers all primary key values from the control version and checks if they are present in the target version for each stream.
49
- If there are missing primary keys, the test fails and the missing records are stored in the test artifacts.
50
- Args:
51
- request (SubRequest): The test request.
52
- record_property (Callable): A callable for stashing information on the report.
53
- streams: (Iterable[str]): The list of streams configured for the connection.
54
- primary_keys_per_stream (Dict[str, Optional[List[str]]]): The primary keys for each stream.
55
- read_with_state_control_execution_result (ExecutionResult): The control version execution result.
56
- read_with_state_target_execution_result (ExecutionResult): The target version execution result.
57
- """
58
- if not read_with_state_control_execution_result.primary_keys_per_stream:
59
- pytest.skip("No primary keys provided on any stream. Skipping the test.")
60
-
61
- logger = get_test_logger(request)
62
- streams_with_missing_records = set()
63
- for stream_name in read_with_state_control_execution_result.configured_streams:
64
- _primary_key = (
65
- read_with_state_control_execution_result.primary_keys_per_stream[
66
- stream_name
67
- ]
68
- )
69
- if not _primary_key:
70
- # TODO: report skipped PK test per individual stream
71
- logger.warning(f"No primary keys provided on stream {stream_name}.")
72
- continue
73
-
74
- primary_key = (
75
- _primary_key[0] if isinstance(_primary_key, list) else _primary_key
76
- )
77
-
78
- control_pks = set()
79
- target_pks = set()
80
- logger.info(
81
- f"Retrieving primary keys for stream {stream_name} on control version."
82
- )
83
- for (
84
- control_record
85
- ) in read_with_state_control_execution_result.get_records_per_stream(
86
- stream_name
87
- ):
88
- control_pks.add(control_record.record.data[primary_key])
89
-
90
- logger.info(
91
- f"Retrieving primary keys for stream {stream_name} on target version."
92
- )
93
- for (
94
- target_record
95
- ) in read_with_state_target_execution_result.get_records_per_stream(
96
- stream_name
97
- ):
98
- target_pks.add(target_record.record.data[primary_key])
99
-
100
- if missing_pks := control_pks - target_pks:
101
- logger.warning(
102
- f"Found {len(missing_pks)} primary keys for stream {stream_name}. Retrieving missing records."
103
- )
104
- streams_with_missing_records.add(stream_name)
105
- missing_records = [
106
- r
107
- for r in read_with_state_control_execution_result.get_records_per_stream(
108
- stream_name
109
- )
110
- if r.record.data[primary_key] in missing_pks
111
- ]
112
- record_property(
113
- f"Missing records on stream {stream_name}",
114
- json.dumps(missing_records),
115
- )
116
- artifact_path = write_string_to_test_artifact(
117
- request,
118
- json.dumps(missing_records),
119
- f"missing_records_{stream_name}.json",
120
- subdir=request.node.name,
121
- )
122
- logger.info(
123
- f"Missing records for stream {stream_name} are stored in {artifact_path}."
124
- )
125
- if streams_with_missing_records:
126
- pytest.fail(
127
- f"Missing records for streams: {', '.join(streams_with_missing_records)}."
128
- )
129
-
130
- async def _check_record_counts(
131
- self,
132
- record_property: Callable,
133
- read_control_execution_result: ExecutionResult,
134
- read_target_execution_result: ExecutionResult,
135
- ) -> None:
136
- record_count_difference_per_stream: dict[str, dict[str, int]] = {}
137
- for stream_name in read_control_execution_result.configured_streams:
138
- control_records_count = sum(
139
- 1
140
- for _ in read_control_execution_result.get_records_per_stream(
141
- stream_name
142
- )
143
- )
144
- target_records_count = sum(
145
- 1
146
- for _ in read_target_execution_result.get_records_per_stream(
147
- stream_name
148
- )
149
- )
150
-
151
- difference = {
152
- "delta": target_records_count - control_records_count,
153
- "control": control_records_count,
154
- "target": target_records_count,
155
- }
156
-
157
- if difference["delta"] != 0:
158
- record_count_difference_per_stream[stream_name] = difference
159
- error_messages = []
160
- for stream, difference in record_count_difference_per_stream.items():
161
- if difference["delta"] > 0:
162
- error_messages.append(
163
- f"Stream {stream} has {difference['delta']} more records in the target version ({difference['target']} vs. {difference['control']})."
164
- )
165
- if difference["delta"] < 0:
166
- error_messages.append(
167
- f"Stream {stream} has {-difference['delta']} fewer records in the target version({difference['target']} vs. {difference['control']})."
168
- )
169
- if error_messages:
170
- record_property("Record count differences", "\n".join(error_messages))
171
- pytest.fail("Record counts are different.")
172
-
173
- async def _check_all_records_are_the_same(
174
- self,
175
- request: SubRequest,
176
- record_property: Callable,
177
- read_control_execution_result: ExecutionResult,
178
- read_target_execution_result: ExecutionResult,
179
- ) -> None:
180
- """This test checks if all records in the control version are present in the target version for each stream.
181
- If there are mismatches, the test fails and the missing records are stored in the test artifacts.
182
- It will catch differences in record schemas, missing records, and extra records.
183
-
184
- Args:
185
- request (SubRequest): The test request.
186
- read_control_execution_result (ExecutionResult): The control version execution result.
187
- read_target_execution_result (ExecutionResult): The target version execution result.
188
- """
189
- streams_with_diff = set()
190
- for stream in read_control_execution_result.configured_streams:
191
- control_records = list(
192
- read_control_execution_result.get_records_per_stream(stream)
193
- )
194
- target_records = list(
195
- read_target_execution_result.get_records_per_stream(stream)
196
- )
197
-
198
- if control_records and not target_records:
199
- pytest.fail(f"Stream {stream} is missing in the target version.")
200
-
201
- if primary_key := read_control_execution_result.primary_keys_per_stream.get(
202
- stream
203
- ):
204
- diffs = self._get_diff_on_stream_with_pk(
205
- request,
206
- record_property,
207
- stream,
208
- control_records,
209
- target_records,
210
- primary_key,
211
- )
212
- else:
213
- diffs = self._get_diff_on_stream_without_pk(
214
- request,
215
- record_property,
216
- stream,
217
- control_records,
218
- target_records,
219
- )
220
-
221
- if diffs:
222
- streams_with_diff.add(stream)
223
-
224
- if streams_with_diff:
225
- messages = [
226
- f"Records for stream {stream} are different. Please check the diff in the test artifacts for debugging."
227
- for stream in sorted(streams_with_diff)
228
- ]
229
- pytest.fail("/n".join(messages))
230
-
231
- def _check_record_schema_match(
232
- self,
233
- request: SubRequest,
234
- record_property: Callable,
235
- control_execution_result: ExecutionResult,
236
- target_execution_result: ExecutionResult,
237
- ) -> None:
238
- """This test checks if the schema of the records in the control and target versions match.
239
- It compares the meta schema inferred for each streams on the control and target versions.
240
- It also fetches an example record for each stream from the DuckDB instance and compares the schema of the records.
241
-
242
- Args:
243
- record_property (Callable): The record property to store the mismatching fields.
244
- control_execution_result (ExecutionResult): The control version execution result.
245
- target_execution_result (ExecutionResult): The target version execution result.
246
- """
247
- logger = get_test_logger(request)
248
-
249
- assert control_execution_result.stream_schemas is not None, (
250
- "Control schemas were not inferred."
251
- )
252
- assert target_execution_result.stream_schemas is not None, (
253
- "Target schemas were not inferred."
254
- )
255
-
256
- mismatches_count = 0
257
- for stream in control_execution_result.stream_schemas:
258
- control_schema = control_execution_result.stream_schemas.get(stream, {})
259
- if not control_schema:
260
- logger.warning(f"Stream {stream} was not found in the control results.")
261
-
262
- target_schema = target_execution_result.stream_schemas.get(stream, {})
263
- if control_schema and not target_schema:
264
- logger.warning(
265
- f"Stream {stream} was present in the control results but not in the target results."
266
- )
267
-
268
- diff = DeepDiff(control_schema, target_schema, ignore_order=True)
269
- if diff:
270
- record_property(
271
- f"{stream} diff between control and target version", diff.pretty()
272
- )
273
- try:
274
- control_record = next(
275
- control_execution_result.get_records_per_stream(stream)
276
- )
277
- control_example = json.dumps(control_record.record.data, indent=2)
278
- record_property(
279
- f"{stream} example record for control version", control_example
280
- )
281
- except StopIteration:
282
- logger.warning(
283
- f"Stream {stream} has no record in the control version."
284
- )
285
- try:
286
- target_record = next(
287
- target_execution_result.get_records_per_stream(stream)
288
- )
289
- target_example = json.dumps(target_record.record.data, indent=2)
290
- record_property(
291
- f"{stream} example record for target version", target_example
292
- )
293
- except StopIteration:
294
- logger.warning(
295
- f"Stream {stream} has no record in the target version."
296
- )
297
- mismatches_count += 1
298
-
299
- if mismatches_count > 0:
300
- pytest.fail(
301
- f"{mismatches_count} streams have mismatching schemas between control and target versions."
302
- )
303
-
304
- @pytest.mark.with_state()
305
- async def test_record_count_with_state(
306
- self,
307
- record_property: Callable,
308
- read_with_state_control_execution_result: ExecutionResult,
309
- read_with_state_target_execution_result: ExecutionResult,
310
- ) -> None:
311
- """This test compares the record counts between the control and target versions on each stream.
312
- Records are pulled from the output of the read command to which the connection state is passed.
313
- It fails if there are any differences in the record counts.
314
- It is not bulletproof, if the upstream source supports insertion or deletion it may lead to false positives.
315
- The HTTP cache used between the control and target versions command execution might limit this problem.
316
- Extra records in the target version might mean that a bug was fixed, but it could also mean that the target version produces duplicates.
317
- We should add a new test for duplicates and not fail this one if extra records are found.
318
- More advanced checks are done in the other tests.
319
- """
320
- fail_test_on_failing_execution_results(
321
- record_property,
322
- [
323
- read_with_state_control_execution_result,
324
- read_with_state_target_execution_result,
325
- ],
326
- )
327
- await self._check_record_counts(
328
- record_property,
329
- read_with_state_control_execution_result,
330
- read_with_state_target_execution_result,
331
- )
332
-
333
- @pytest.mark.without_state()
334
- async def test_record_count_without_state(
335
- self,
336
- record_property: Callable,
337
- read_control_execution_result: ExecutionResult,
338
- read_target_execution_result: ExecutionResult,
339
- ) -> None:
340
- """This test compares the record counts between the control and target versions on each stream.
341
- Records are pulled from the output of the read command to which no connection state is passed (leading to a full-refresh like sync).
342
- It fails if there are any differences in the record counts.
343
- It is not bulletproof, if the upstream source supports insertion or deletion it may lead to false positives.
344
- The HTTP cache used between the control and target versions command execution might limit this problem.
345
- Extra records in the target version might mean that a bug was fixed, but it could also mean that the target version produces duplicates.
346
- We should add a new test for duplicates and not fail this one if extra records are found.
347
- More advanced checks are done in the other tests.
348
- """
349
- fail_test_on_failing_execution_results(
350
- record_property,
351
- [
352
- read_control_execution_result,
353
- read_target_execution_result,
354
- ],
355
- )
356
- await self._check_record_counts(
357
- record_property,
358
- read_control_execution_result,
359
- read_target_execution_result,
360
- )
361
-
362
- @pytest.mark.with_state()
363
- async def test_all_pks_are_produced_in_target_version_with_state(
364
- self,
365
- request: SubRequest,
366
- record_property: Callable,
367
- read_with_state_control_execution_result: ExecutionResult,
368
- read_with_state_target_execution_result: ExecutionResult,
369
- ) -> None:
370
- """This test checks if all primary key values produced by the control version are present in the target version for each stream.
371
- It is reading the records from the output of the read command to which the connection state is passed.
372
- A failing test means that the target version is missing some records.
373
- """
374
- fail_test_on_failing_execution_results(
375
- record_property,
376
- [
377
- read_with_state_control_execution_result,
378
- read_with_state_target_execution_result,
379
- ],
380
- )
381
- await self._check_all_pks_are_produced_in_target_version(
382
- request,
383
- record_property,
384
- read_with_state_control_execution_result,
385
- read_with_state_target_execution_result,
386
- )
387
-
388
- @pytest.mark.without_state()
389
- async def test_all_pks_are_produced_in_target_version_without_state(
390
- self,
391
- request: SubRequest,
392
- record_property: Callable,
393
- read_control_execution_result: ExecutionResult,
394
- read_target_execution_result: ExecutionResult,
395
- ) -> None:
396
- """This test checks if all primary key values produced by the control version are present in the target version for each stream.
397
- Records are pulled from the output of the read command to which no connection state is passed (leading to a full-refresh like sync).
398
- A failing test means that the target version is missing some records.
399
- """
400
- fail_test_on_failing_execution_results(
401
- record_property,
402
- [
403
- read_control_execution_result,
404
- read_target_execution_result,
405
- ],
406
- )
407
- await self._check_all_pks_are_produced_in_target_version(
408
- request,
409
- record_property,
410
- read_control_execution_result,
411
- read_target_execution_result,
412
- )
413
-
414
- @pytest.mark.with_state()
415
- async def test_record_schema_match_with_state(
416
- self,
417
- request: SubRequest,
418
- record_property: Callable,
419
- read_with_state_control_execution_result: ExecutionResult,
420
- read_with_state_target_execution_result: ExecutionResult,
421
- ) -> None:
422
- """This test checks if the schema of the streams in the control and target versions match.
423
- It produces a meta schema for each stream on control and target version and compares them.
424
- It is not using the catalog schema, but inferring schemas from the actual records produced by the read command.
425
- Records are pulled from the output of the read command to which the connection state is passed.
426
- """
427
- self._check_record_schema_match(
428
- request,
429
- record_property,
430
- read_with_state_control_execution_result,
431
- read_with_state_target_execution_result,
432
- )
433
-
434
- @pytest.mark.without_state()
435
- async def test_record_schema_match_without_state(
436
- self,
437
- request: SubRequest,
438
- record_property: Callable,
439
- read_control_execution_result: ExecutionResult,
440
- read_target_execution_result: ExecutionResult,
441
- ) -> None:
442
- """This test checks if the schema of the streams in the control and target versions match.
443
- It produces a meta schema for each stream on control and target version and compares them.
444
- It is not using the catalog schema, but inferring schemas from the actual records produced by the read command.
445
- Records are pulled from the output of the read command to which the connection state is passed.
446
- """
447
- self._check_record_schema_match(
448
- request,
449
- record_property,
450
- read_control_execution_result,
451
- read_target_execution_result,
452
- )
453
-
454
- @pytest.mark.allow_diagnostic_mode
455
- @pytest.mark.with_state()
456
- async def test_all_records_are_the_same_with_state(
457
- self,
458
- request: SubRequest,
459
- record_property: Callable,
460
- read_with_state_control_execution_result: ExecutionResult,
461
- read_with_state_target_execution_result: ExecutionResult,
462
- ) -> None:
463
- """This test compares all records between the control and target versions on each stream.
464
- It is very sensitive to record schema and order changes.
465
- It fails if there are any differences in the records.
466
- It is reading the records from the output of the read command to which the connection state is passed.
467
- """
468
- fail_test_on_failing_execution_results(
469
- record_property,
470
- [
471
- read_with_state_control_execution_result,
472
- read_with_state_target_execution_result,
473
- ],
474
- )
475
- await self._check_all_records_are_the_same(
476
- request,
477
- record_property,
478
- read_with_state_control_execution_result,
479
- read_with_state_target_execution_result,
480
- )
481
-
482
- @pytest.mark.allow_diagnostic_mode
483
- @pytest.mark.without_state()
484
- async def test_all_records_are_the_same_without_state(
485
- self,
486
- request: SubRequest,
487
- record_property: Callable,
488
- read_control_execution_result: ExecutionResult,
489
- read_target_execution_result: ExecutionResult,
490
- ) -> None:
491
- """This test compares all records between the control and target versions on each stream.
492
- It is very sensitive to record schema and order changes.
493
- It fails if there are any differences in the records.
494
- It is reading the records from the output of the read command to which no connection state is passed (leading to a full-refresh like sync).
495
- """
496
- fail_test_on_failing_execution_results(
497
- record_property,
498
- [
499
- read_control_execution_result,
500
- read_target_execution_result,
501
- ],
502
- )
503
- await self._check_all_records_are_the_same(
504
- request,
505
- record_property,
506
- read_control_execution_result,
507
- read_target_execution_result,
508
- )
509
-
510
- def _get_diff_on_stream_with_pk(
511
- self,
512
- request: SubRequest,
513
- record_property: Callable,
514
- stream: str,
515
- control_records: list[AirbyteMessage],
516
- target_records: list[AirbyteMessage],
517
- primary_key: list[str],
518
- ) -> Optional[Iterable[str]]:
519
- control_pks = {r.record.data[primary_key[0]] for r in control_records}
520
- target_pks = {r.record.data[primary_key[0]] for r in target_records}
521
-
522
- # Compare the diff for all records whose primary key is in
523
- record_diff_path_prefix = f"{stream}_record_diff"
524
- record_diff = get_and_write_diff(
525
- request,
526
- _get_filtered_sorted_records(
527
- control_records, target_pks, True, primary_key
528
- ),
529
- _get_filtered_sorted_records(
530
- target_records, control_pks, True, primary_key
531
- ),
532
- record_diff_path_prefix,
533
- ignore_order=False,
534
- exclude_paths=EXCLUDE_PATHS,
535
- )
536
-
537
- control_records_diff_path_prefix = f"{stream}_control_records_diff"
538
- control_records_diff = get_and_write_diff(
539
- request,
540
- _get_filtered_sorted_records(
541
- control_records, target_pks, False, primary_key
542
- ),
543
- [],
544
- control_records_diff_path_prefix,
545
- ignore_order=False,
546
- exclude_paths=EXCLUDE_PATHS,
547
- )
548
-
549
- target_records_diff_path_prefix = f"{stream}_target_records_diff"
550
- target_records_diff = get_and_write_diff(
551
- request,
552
- [],
553
- _get_filtered_sorted_records(
554
- target_records, control_pks, False, primary_key
555
- ),
556
- target_records_diff_path_prefix,
557
- ignore_order=False,
558
- exclude_paths=EXCLUDE_PATHS,
559
- )
560
-
561
- has_diff = record_diff or control_records_diff or target_records_diff
562
-
563
- if has_diff:
564
- record_property(
565
- f"{stream} stream: records with primary key in target & control whose values differ",
566
- record_diff,
567
- )
568
- record_property(
569
- f"{stream} stream: records in control but not target",
570
- control_records_diff,
571
- )
572
- record_property(
573
- f"{stream} stream: records in target but not control",
574
- target_records_diff,
575
- )
576
-
577
- return (record_diff, control_records_diff, target_records_diff)
578
- return None
579
-
580
- def _get_diff_on_stream_without_pk(
581
- self,
582
- request: SubRequest,
583
- record_property: Callable,
584
- stream: str,
585
- control_records: list[AirbyteMessage],
586
- target_records: list[AirbyteMessage],
587
- ) -> Optional[Iterable[str]]:
588
- diff = get_and_write_diff(
589
- request,
590
- [json.loads(r.record.json(sort_keys=True)) for r in control_records],
591
- [json.loads(r.record.json(sort_keys=True)) for r in target_records],
592
- f"{stream}_diff",
593
- ignore_order=True,
594
- exclude_paths=EXCLUDE_PATHS,
595
- )
596
- if diff:
597
- record_property(f"Diff for stream {stream}", diff)
598
- return (diff,)
599
- return None
600
-
601
-
602
- def _get_filtered_sorted_records(
603
- records: list[AirbyteMessage],
604
- primary_key_set: set[Generator[Any, Any, None]],
605
- include_target: bool,
606
- primary_key: list[str],
607
- ) -> list[dict]:
608
- """
609
- Get a list of records sorted by primary key, and filtered as specified.
610
-
611
- For example, if `include_target` is true, we filter the records such that
612
- only those whose primary key is in `primary_key_set` are returned.
613
- If `include_target` is false, we only return records whose primary key
614
- is not in `primary_key_set`.
615
- """
616
- if include_target:
617
- _filter = lambda x: x["data"].get(primary_key[0]) in primary_key_set
618
- else:
619
- _filter = lambda x: x["data"].get(primary_key[0]) not in primary_key_set
620
-
621
- return sorted(
622
- filter(
623
- _filter,
624
- [json.loads(s.record.json(sort_keys=True)) for s in records],
625
- ),
626
- key=lambda x: x["data"][primary_key[0]],
627
- )
@@ -1,43 +0,0 @@
1
- # Copyright (c) 2023 Airbyte, Inc., all rights reserved.
2
- from __future__ import annotations
3
-
4
- from collections.abc import Callable
5
-
6
- import pytest
7
- from airbyte_protocol.models import Type # type: ignore
8
- from live_tests.commons.models import ExecutionResult
9
- from live_tests.utils import fail_test_on_failing_execution_results
10
-
11
- pytestmark = [
12
- pytest.mark.anyio,
13
- ]
14
-
15
-
16
- async def test_spec_passes_on_both_versions(
17
- record_property: Callable,
18
- spec_control_execution_result: ExecutionResult,
19
- spec_target_execution_result: ExecutionResult,
20
- ) -> None:
21
- """This test runs the spec command on both the control and target connectors.
22
- It makes sure that the spec command succeeds on both connectors by checking the presence of a SPEC message.
23
- """
24
- fail_test_on_failing_execution_results(
25
- record_property,
26
- [
27
- spec_control_execution_result,
28
- spec_target_execution_result,
29
- ],
30
- )
31
-
32
- def has_spec(execution_result: ExecutionResult) -> bool:
33
- for message in execution_result.airbyte_messages:
34
- if message.type is Type.SPEC and message.spec:
35
- return True
36
- return False
37
-
38
- if not has_spec(spec_control_execution_result):
39
- pytest.skip("The control spec did not succeed, we cannot compare the results.")
40
- if not has_spec(spec_target_execution_result):
41
- pytest.fail(
42
- "The target spec did not succeed. Check the test artifacts for more information."
43
- )