Skip to content

Test case PUT-create and CSV import skip TestCaseRepository.prepare's create-time checks #35667

Open

Description

@TeddyCr

Affected module

Backend

Describe the bug

TestCaseRepository.prepare holds every create-time check for test cases: the disabled-definition gate (if (!update && …getEnabled())), validateTestParameters, validateColumnTestCase, validateDimensionColumns and setDataQualityDimension. Two create paths don't reach prepare correctly, so these checks are skipped:

Gap 1: PUT that creates a test case runs prepare with update = true
TestCaseResource.createOrUpdate calls repository.prepareInternal(test, true) unconditionally (TestCaseResource.java:893), before it knows whether the row exists. It then calls repository.createOrUpdate, which creates through createNewEntity and never prepares again (EntityRepository.java:4305-4313). As a result, every if (!update) gate in TestCaseRepository.prepare is skipped when a test case is created by PUT, which is how ingestion (dbt, Great Expectations, the SDKs) creates test cases. Today that skips the disabled-definition check (TestCaseRepository.java:888).

EntityResource.createOrUpdate doesn't have this problem: it routes creates through repository.create, which prepares with false. MetricResource.preauthorizeHierarchyUpdate and EntityCsv.createEntity both look the row up first.

Gap 2: the test case CSV import never calls prepare
TestCaseCsv.createEntity (TestCaseRepository.java:2503) creates and updates through createOrUpdateForImport, which calls neither prepare nor prepareInternal. The code says so itself at TestCaseRepository.java:2596-2597. The generic EntityCsv.createEntity does call it, with prepareInternal(entity, isUpdate). As a result:

  • A real (non-dry-run) import runs none of the checks above, for new rows or for updates.
  • A dry run calls prepareInternal(testCase, true) for every row (TestCaseRepository.java:2622). It checks parameters but skips the create-only gates. Dry run and real import can therefore disagree on the same file: a row that fails validateTestParameters fails the dry run but is stored by the real import.
  • repository.getOrCreateTestSuite(testCase) (TestCaseRepository.java:2583) runs before any check and also during a dry run, so dry runs and rejected rows can create basic test suites.

To Reproduce

Gap 1:

  1. Disable a test definition: PATCH /v1/dataQuality/testDefinitions/{id}, setting enabled to false.
  2. POST /v1/dataQuality/testCases with that definition. Result: 400, "Test definition '…' is disabled and cannot be used to create test cases".
  3. PUT /v1/dataQuality/testCases with the same body and a new name. Result: 201, created.

Gap 2:

  1. Import a test case CSV (PUT /v1/dataQuality/testCases/name/{name}/import?dryRun=false) with a new row that uses the disabled definition, or that omits a required parameter. Result: the row is created.
  2. Run the same file with dryRun=true. Result: the missing-parameter row fails and the disabled-definition row passes.

Expected behavior

Every create path runs TestCaseRepository.prepare with update = false, and every update path runs it with update = true. A test case is then held to the same checks whether it's created by POST, PUT, createMany or CSV import, and a dry run reports what the real import would do.

What needs to change

  1. TestCaseResource.createOrUpdate (PUT). Replace repository.prepareInternal(test, true) with a lookup, then pass the real value:

    repository.setFullyQualifiedName(test);
    boolean exists = repository.findByNameOrNull(test.getFullyQualifiedName(), Include.ALL) != null;
    repository.prepareInternal(test, exists);

    Use Include.ALL so the result matches the lookup EntityRepository.createOrUpdate does next.

  2. TestCaseCsv.createEntity (CSV import). Follow EntityCsv.createEntity:

    • Set the FQN.
    • Look up the existing row and derive isUpdate.
    • Call repository.prepareInternal(testCase, isUpdate) for both the dry run and the real import, before createOrUpdateForImport.

    prepare assigns the table's basic suite (TestCaseRepository.java:906-907), which would overwrite the suite the row resolved from its testSuite column or the target bundle suite. Resolve that by applying the row's suite after prepare, or by having prepare keep a suite the caller already set.

  3. CSV dry run. Replace prepareInternal(testCase, true) with prepareInternal(testCase, isUpdate). Stop calling getOrCreateTestSuite before validation, so a dry run or a rejected row doesn't create a basic suite.

  4. Remove the workarounds in Fixes 35274: Check test parameters against their definitions and fix open-window and boolean-set runs #35629. Fixes 35274: Check test parameters against their definitions and fix open-window and boolean-set runs #35629 adds definition-conformance checks for test cases and works around these gaps. Once items 1–3 land, remove whatever remains of those workarounds, such as a new-row conformance check in TestCaseCsv, and update the Consequences section of ADR:2026-10-12-test-cases-are-checked-against-their-definition-at-create, which records the CSV import as not telling new rows from existing ones.

Tests (TestCaseResourceIT)

  • PUT-create with a disabled definition returns 4xx, and no basic test suite is created for a table that had none.
  • PUT on an existing test case whose definition was disabled afterwards returns 200, so the row stays editable.
  • CSV import, real and dry run: a new row with a disabled definition fails.
  • CSV import, real and dry run: a new row missing a required parameter fails.
  • CSV import: updating an existing row still succeeds.
  • The same CSV gives the same per-row result with dryRun=true and dryRun=false.
  • A dry run creates no test suite.

OpenMetadata version

main (upstream/main at the time of filing)

Additional context

Found while reviewing #35629 (#35629 (review)).

Activity

  1. self-assigned this
    on Oct 16, 2026
  2. added this to Shipping and removed this from Roadmapon Oct 16, 2026
  3. moved this to Release Backlog 🚧 in Shippingon Oct 16, 2026
  4. added theissue type on Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

  • Status
    Release Backlog 🚧

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions