Repository navigation
feat(standard-tests): check primary keys, state messages and error traces in read tests, and report stream statuses - #1230
Conversation
…aces in read tests The Standard Tests replaced the Connector Acceptance Tests as the only read coverage for most connectors, but three checks of CAT's basic read test were not carried over. This restores them: - Primary keys: for every `basic_read` config, every record of a stream with a `source_defined_primary_key` must have a usable key. No key field may hold an object or an array, and not every key field may be missing or null, as in CAT's `validate_primary_keys_data_type`. Failures name the stream, record number and key path, never the value. Opt out per config with `validate_primary_keys_data_type: false`. - State: every configured stream must be named by a STREAM state message or by a GLOBAL state's `stream_states`; no state may use the LEGACY type; every state must carry `sourceStats.recordCount`. CAT only compared the number of state messages with the number of streams, which one stream's many states could satisfy for another stream that emitted none. Opt out per config with `validate_state_messages: false`. - Error traces: the new `test_fail_read_with_invalid_config` reads with a config the connector's own spec rejects (it holds none of the required top-level properties and no real value, so no request can be sent with real credentials) and requires a well-formed ERROR trace, as CAT's `test_airbyte_trace_message_on_failure` did. `test_fail_read_with_bad_catalog` now checks its error traces the same way. Opt out with `expect_trace_message_on_failure: false`. The new `assert_read_records` arguments default to off, so the Docker read test is unchanged; it can pass them once it collects record messages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ss_reason A `basic_read` entry has no `bypass_reason` field: pydantic ignores the key silently, and only `empty_streams` items carry one. Tell readers to give the reason in a YAML comment, as the 7.36.0 `validate_schema` step and the connector opt-outs do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksTesting This CDK VersionYou can test this version of the CDK using the following: # Run the CLI from this branch:
uvx 'git+https://github.com/airbytehq/airbyte-python-cdk.git@tolik0/cdk/read-assertions-slice-2#egg=airbyte-python-cdk[dev]' --help
# Update a connector to use the CDK from this branch ref:
cd airbyte-integrations/connectors/source-example
poe use-cdk-branch tolik0/cdk/read-assertions-slice-2PR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
|
PyTest Results (Fast)6 184 tests +230 6 153 ✅ +230 6m 23s ⏱️ +53s Results for commit f547853. ± Comparison against base commit d5dd299. This pull request removes 2 and adds 232 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
PyTest Results (Full)6 187 tests 6 156 ✅ 12m 5s ⏱️ Results for commit f547853. ♻️ This comment has been updated with latest results. |
Resolve the cdk-migrations.md conflict with #1226: its section ships as 7.39.0, so it is renamed "Upgrading to 7.39.0" below this change's "(unreleased)" section. #1226 lists `validate_state_messages` and `expect_trace_message_on_failure` in `LEGACY_ENTRY_KEYS["basic_read"]`, so the validator dropped both before the fields this change adds could read them, and `validate_state_messages: false` was silently ignored. Remove them from the mapping, assert that `get_scenarios()` keeps them, and say in the migration guide that 7.39.0 accepted and ignored them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tions-slice-2 # Conflicts: # cdk-migrations.md
…e-test-config.yml Since #1228, get_scenarios() raises MissingAcceptanceTestConfigError when a connector has no acceptance-test-config.yml, so the tmp connector of the test_fail_read_with_invalid_config tests needs one. The suite helper now writes a file with one basic_read config unless the test wrote its own.
…read test test_basic_read now checks the stream status trace messages of every basic_read config, as CAT's validate_stream_statuses did: every configured stream, and no other, reports STARTED, then only RUNNING, then COMPLETE. The check is advisory. Its problems are reported as a StreamStatusWarning, exported from airbyte_cdk.test.standard_tests, so the fleet can be measured before it is enforced. It adds no config key. The fake sources of the read unit tests now emit the statuses the CDK emits.
…d-catalog test and read basic_read opt-outs only from basic_read entries CAT's expect_trace_message_on_failure skipped test_airbyte_trace_message_on_failure, which is test_fail_read_with_bad_catalog here; that test now skips per config as in CAT, and test_fail_read_with_invalid_config keeps honouring the flag. validate_primary_keys_data_type, validate_state_messages and expect_trace_message_on_failure are rejected in entries outside basic_read again: merged into the config's scenario, they would silently turn its basic_read checks off.
Baz (bazarnov)
left a comment
There was a problem hiding this comment.
Thanks for the thorough work. The checks match CAT where the body says they do, and test_fail_read_with_invalid_config fails no live manifest-only source on its own check: of 507, 479 pass, 16 skip on a spec without top-level required, and 12 already fail (11 at collection on the legacy tests: layout, source-braze on importing its components). Three comments should land before merge: the test for the bad-catalog check, the Python-source rollout, and the 7.41.0 range. The rest are optional.
- airbytehq/airbyte#88937: verified locally with this branch and the connectors' test secrets. Without the opt-outs only the primary key check fails, with them both connectors pass, and on 7.41.0 both fail collection, so the merge order in the body holds.
- #1231 conflicts with this PR in
cdk-migrations.mdand callsassert_read_recordswithout the new arguments; after its rebase it should pass them. - Behind failures that already happen (manifest and code read only, unverified): source-coingecko-coins
market_chart(the primary keypricesis an array in its schema), and source-firebase-realtime-database (a Python source that emits no state; its legacytests:layout already fails collection).
- Pin the bad-catalog error trace check with a source that emits a malformed trace. - Read validate_schema only from basic_read entries, like the other basic_read opt-outs. - Name the likely cause when the invalid-config read succeeds. - Report a STREAM state without stream, or a GLOBAL state without global, as such instead of as LEGACY; test the untyped state. - Test the record path guard, the invalid record count, recordCount None, a boolean emitted_at, and that stream statuses are reported when an enforced check fails. - cdk-migrations.md: Python sources get the checks at their CDK bump; 7.39.0 through 7.41.0; all three state checks hold for a CDK connector that reads every stream to the end.
Baz (bazarnov)
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround. All eight points are addressed and pinned: I re-ran the mutations from my review on f547853, and each now fails a test (11 of 11, including the new STREAM/GLOBAL-without-object branch, the validate_schema rule and the invalid-config message). The new validate_schema rule turns nothing red: all 8 monorepo uses sit in basic_read, and no airbyte-enterprise config sets it. CI is green except Check: destination-motherduck, which fails on other PRs too (dead MotherDuck token).
Merge order as in the body: this PR >> 7.42.0 >> airbytehq/airbyte#88937. Its description still says to merge it first; the order here is the right one.
Part of airbytehq/airbyte-internal-issues#17135 (P1.2 "Restore read assertions", epic airbytehq/airbyte-internal-issues#17129). Follows #1218.
What
#1218 restored per-stream record presence and schema validation in
test_basic_read. Four more checks of CAT's basic read test were still missing from the Standard Tests. This PR enforces three of them, with CAT's config flags as the opt-outs, and reports the fourth without enforcing it:_validate_primary_keys_data_type). For every config underbasic_read, every record of a stream with asource_defined_primary_keymust have a usable key: no key field holds an object or an array, and not every key field is missing or null. Opt out withvalidate_primary_keys_data_type: false._validate_state_messages). Every stream the read covers must be named by aSTREAMstate message (or aGLOBALstate'sstream_states); no state uses the deprecatedLEGACYtype; every state carriessourceStats.recordCount. Opt out withvalidate_state_messages: false.test_airbyte_trace_message_on_failure).test_fail_read_with_bad_catalog, which is CAT's test, now checks that its error traces are well-formed, and a new per-connector testtest_fail_read_with_invalid_configdoes the same for a config the spec rejects. Opt out withexpect_trace_message_on_failure: falsein abasic_readentry: as in CAT, it skips the bad-catalog test for that config, and it skips the new test for the connector._validate_stream_statuses). For every config underbasic_read, every stream the read covers, and no other stream, should reportSTARTED, then onlyRUNNING, thenCOMPLETE. Problems are reported as aStreamStatusWarning(exported fromairbyte_cdk.test.standard_tests, likeDiscoveredCatalogWarning) and do not fail the test. There is no config key;-W error::airbyte_cdk.test.standard_tests.StreamStatusWarningmakes it fail.Failure messages name the stream, the record number and the key path, never a value:
How
_read_assertions.py:_StreamPrimaryKeyChecker(runs in the same single pass over the records as the schema check),_find_state_message_failures,assert_error_trace_messages,make_invalid_config.assert_read_recordsgainsvalidate_primary_keysandstate_messages, both default off, so the Docker read test, which P1.4 (airbytehq/airbyte-internal-issues#17137) is changing in parallel, is not touched. It takes record messages plus the configured catalog, so P1.4 can pass the new arguments as is.ConnectorTestScenario:validate_primary_keys_data_type,validate_state_messages,expect_trace_message_on_failure, with CAT's names and defaults (true)._dedup_scenarioskeeps an opt-out declared by any entry for the config, as it already did forvalidate_schema. These three keys andvalidate_schemaare accepted only inbasic_readentries (BASIC_READ_ONLY_FIELDS), as in CAT, so an entry of another section cannot turn the config'sbasic_readchecks off through the merge. For the three new keys that is howmainbehaves;validate_schemawas accepted in any section until now, and no monorepo config sets it outsidebasic_read._read_assertions.py:find_stream_status_problemsandStreamStatusWarning;test_basic_readwarns with the problems before the enforced checks run, so a config that fails them still reports its stream statuses.test_basic_readpasses the new arguments forbasic_readconfigs. A config read only because offull_refreshkeeps the "some records" floor, as in feat(standard-tests): assert per-stream records and record schemas in the basic read test #1218.cdk-migrations.md: a section under "Upgrading to the next minor release (unreleased)".Decisions
Stream statuses are advisory, with no config key. CAT ran
_validate_stream_statusesby default for certified connectors only, and refused to turn it off for them. The Standard Tests have no notion of a certified connector (onlytest_strictness_level, which P3.3, airbytehq/airbyte-internal-issues#17146, is replacing), and the check has never been measured across the fleet. Reporting it as a warning gets that measurement from every CI run without turning anything red. Enforcing it, and for which connectors, is a later change.Primary keys follow CAT exactly. A composite key may have null parts as long as one part is present, and booleans count as scalars (the message calls them "boolean", not "integer"). The stricter "every key part present" rule was measured only (see below), not enforced. As in CAT,
validate_primary_keys_data_type: falseis allowed attest_strictness_level: high; onlyvalidate_schema: falseis refused there.State: the per-stream version, not CAT's count. CAT asserted
len(state_messages) >= len(streams). That is not vacuous, but one stream that checkpoints many times hides another stream that emitted no state at all. This PR checks coverage per stream (matched on namespace and name). The LEGACY check is kept; CAT's version crashed onGLOBALstates, which this one handles. ThesourceStatscheck is kept, but it is vacuous for CDK connectors, because the CDK entrypoint setssourceStats.recordCounton every state it emits; it guards non-CDK sources and the Docker path. The CDK emits a final{"__ab_no_cursor_state_message": true}state for every full-refresh stream it completes, so a CDK connector fails only when a stream ends abnormally or custom code drops the state.The failure input for the trace test is a config the connector's spec rejects. CAT's trace test used the valid config plus a broken catalog, and FAST's
test_fail_read_with_bad_catalogalready is that test. It now uses the same well-formedness check instead of only requiring some trace message, so there is no duplicate. The new test needs a different failure, one that reaches the connector:__airbyte_standard_tests_invalid_config) and none of the spec's required top-level properties. The CDK entrypoint rejects it against the spec with aconfig_errortrace, before any request is sent.basic_readconfigs are all secret-backed are covered too.AbstractSourcereports it asINCOMPLETEwithout an error.basic_readentry setsexpect_trace_message_on_failure: false: the connector does not promise an error trace for a failed read. CAT had no such test; its flag skipped the bad-catalog test, andtest_fail_read_with_bad_catalognow honours it per config too.entrypoint_wrapperturns any uncaught exception into a well-formed trace message, so this check mostly guards the CDK and the harness. The same helper is meant for the Docker path (P1.4), where a crashing image can emit nothing.source-s3's hand-ported CAT harness (integration_tests/test_acceptance.py), named as prior art in the issue, has none of these three checks, so they are ported from CAT's semantics directly.Fleet impact
I ran this branch's checks in-process (as
airbyte-cdk connector testdoes) against airbytehq/airbytemasterat1e22e3c689c. The population was every manifest-only source with abasic_readconfig: 185 connectors. 16 were skipped, because their test secret holds a single-use rotating refresh token, and a local run would invalidate the copy CI uses. Eachbasic_readscenario was read once (20-minute timeout, and 45 minutes for a second try of the 6 that timed out), and the verdict onmain(presence and schema, from #1218) and the new checks were taken from the same read.test_fail_read_with_invalid_configran once per connector, andtest_fail_read_with_bad_catalogon the first read scenario.Already red before this change: 41 have expired, revoked or under-scoped test credentials, or hit plan limits; 8 have no test secret in GSM; 3 fail to load with the current CDK or have an
acceptance-test-config.ymlthe harness cannot parse; 25 fail on other API or connector errors, or on #1218's checks. None of the reads that reached the checks failed the new ones, so they hide nothing that is known.colorscalendarandeventhold objects.subscribers_histogram_by_stateid, the declared key.Both are real key problems, not false positives, and CAT would have failed them too. Changing a primary key is a breaking change, so airbytehq/airbyte#88937 opts the two configs out with
validate_primary_keys_data_type: falseinstead, and the key fixes are left for separate PRs.Per check:
validate_state_messages: false). No connector fails. source-monday also declares the opt-out; it was among the 16 connectors skipped for their rotating refresh token.test_fail_read_with_invalid_config: 161 passed and 6 skipped because the spec requires no top-level property. 2 failed, both on connectors whose every test already fails (one cannot load its custom components with the current CDK, one has anacceptance-test-config.ymlthe harness cannot parse).test_fail_read_with_bad_catalog: 159 passed. The 9 failures are connectors that are red before: 8 have no test secret, and 1 cannot load its custom components.The CDK's Python test connectors, run in-process with this branch: source-hardcoded-records passes (the new trace test skips, as its spec requires nothing). source-shopify's
test_fail_read_with_invalid_configpasses; its twotest_basic_readscenarios fail before any new check, because the test store'sEurope/Kyivtime zone is missing from thependulum2 time zone data, which this PR does not touch. source-google-drive cannot be created by the harness (its constructor needscatalog,configandstate), and the new trace test skips on its existingexpect_trace_message_on_failure: false.Python sources. Since airbytehq/airbyte#88945, Python sources run this suite in-process with the CDK they pin. I ran the 26 that pin a CDK with the in-process suite (6.46.0 or later) against airbytehq/airbyte
masterateaf94ec6fea, as a CDK bump would: each connector's own lock on Python 3.11, with this branch's wheel (at cb7d7d6) installed over the locked CDK. None uses a rotating refresh token. 17 ship their ownintegration_tests/standard_tests.py.basic_readscenarios, 47 reached the checks, including all 14 of source-azure-blob-storage, all 21 of source-s3, all 3 of source-gcs and both of source-google-drive. All 47 pass the primary key and state checks, and none reports aStreamStatusWarning. 13 failed before any check, the same way onmain: expired or rejected credentials (appsflyer, asana, freshcaller, jina-ai-reader, microsoft-sharepoint), API errors (cart), noconfig.jsonin the test secret (box-data-extract), and two failures that only happen locally (faker's processes under macOS spawn, and google-ads, whose gRPC calls cannot pass the local TLS-inspecting proxy). source-google-drive was rerun at f547853 with the proxy's CA bundle set forhttplib2; its first run had failed to list files for that reason. The other 3 belong to source-facebook-marketing and source-shopify, whose own suites overridetest_basic_read: they get the new read checks only once that override is removed.test_fail_read_with_invalid_config: 16 passed and 7 skipped. 3 failed at collection on the legacy top-leveltests:layout, as every test of those connectors already does (amazon-sqs, commercetools, firebase-realtime-database).test_fail_read_with_bad_catalog: 20 passed and 1 skipped (source-google-drive, on itsexpect_trace_message_on_failure: false, which this test honours since f547853); 1 is red before (box-data-extract, noconfig.json).Rollout
The connector monorepo installs the
airbyte-cdkCLI unpinned, so manifest-only connectors, which run the in-process suite throughairbyte-cdk connector test, pick the new checks up with the release, without a per-connector CDK bump. Python sources run the same suite in-process with the CDK they pin (airbytehq/airbyte#88945), so they get the checks when they bump to this release: 22 of the 58 live Python sources allow it, including all 9 certified ones. 17 Python sources ship their ownintegration_tests/standard_tests.py; source-facebook-marketing and source-shopify overridetest_basic_read, so the new read checks reach them only once the override goes. The Python-source run under "Fleet impact" covers the 26 that run the suite today. This ships as a minor (feat, no!), as standard-test changes do.Merge order:
#88937 cannot go first. It adds
validate_primary_keys_data_type: falseto twobasic_readentries, and releases 7.39.0 through 7.41.0 reject that key as one the standard tests do not read. With the unpinned CLI, both connectors would then fail scenario collection, and so every test, until this PR is released. Its green checks predate 7.39.0: they ran on 2026-10-14, and loading its two configs with 7.41.0 fails with that error today.In this order, between the release and the merge of #88937, the two connectors fail only the new primary key check, and only on a PR that touches them.
Landing order for airbytehq/airbyte-internal-issues#17135 (updated)
fail_on_extra_columns) and data points (validate_data_points) behind flags, since CAT never appliedfail_on_extra_columnsand defaultedvalidate_data_pointsto false.The Docker read test (
test_docker_image_build_and_read) is not changed here; P1.4 (airbytehq/airbyte-internal-issues#17137) owns it.Test plan
poetry run pytest unit_tests/test/ -p no:cacheprovider: 978 passed, 9 skipped (at f547853). With-W error::airbyte_cdk.test.standard_tests.StreamStatusWarning, onlytest_basic_read_fails_without_a_declared_status[no_records]fails, because its fake source emits nothing.source_pokeapi_w_components_pytest_basic_readwith-W error::airbyte_cdk.test.standard_tests.StreamStatusWarning: passes, so a real declarative source reports complete stream statuses.unit_tests/test/test_read_keys_state_and_traces.pycover each check, including the stream status warning, directly and throughSourceTestSuiteBase(fake sources that emit a null key, no state, a malformed trace, or a read that succeeds on the invalid config), the opt-outs (includingexpect_trace_message_on_failureskippingtest_fail_read_with_bad_catalog), high strictness, and opt-outs surviving_dedup_scenariosin both orders.unit_tests/test/test_scenario_keys.pychecks that eachbasic_readopt-out is rejected in every other section, from the file and throughget_scenarios(), and still accepted when a scenario is merged or copied. Each of these changes now fails a test: reverting the bad-catalog check toassert result.trace_messages, dropping the record path guard, counting invalid records per problem, dropping therecordCount is Nonecheck, the boolean guard onemitted_at, theSTREAMtype check or theSTREAM/GLOBALwithout object case, the invalid-configassert result.errors, orvalidate_schemafromBASIC_READ_ONLY_FIELDS. A test also pins that stream statuses are reported when an enforced check fails.ruff format,ruff check,mypy airbyte_cdk/test: clean.source_pokeapi_w_components_pystandard tests (the CDK fixture): 11 passed, 11 skipped;test_fail_read_with_invalid_config,test_fail_read_with_bad_catalogandtest_basic_readpass. The extra skip is the spec backward-compatibility test: the fixture's version is older than the published one.🤖 Generated with Claude Code