OpenMetadata/ingestion/tests/integration/ometa/test_ometa_data_contract_api.py
Ram Narayan Balaji 5cb33ce78a
Implementation of Adding Entity Status and Reviewers to assets (#22904)
* Initial Implementation of Adding Status and Reviewers to assets for workflows

* Update generated TypeScript types

* Copilot Review Comments Addressed

* Removed DataProduct Reviewer Inheritance as it is irrelevant

* Commit: Classification has status and reviewers, DataContract uses the same status enums, changed the logic to be APPROVED instead of Active, DataContract can have null status as seen in tests, Changed Workflow to use workflowStatus instead of status as it is contradicting with the approval status, Fixed Tests

* Default for reviewers is null

* Default for reviewers is createSchema

* Addressed CoPilots comments

* Update generated TypeScript types

* Workflow status to workflowStatus in db and migrations

* Revert "Workflow status to workflowStatus in db and migrations"

This reverts commit 676e8789358654bc6f980f855c372f33c22fc40b.

* Changed status to entityStatus in the schema files

* Java Implementation of Default Status, Search Client improvements and Test fixes and new tests

* Adding entityStatus and reviewers in the searchIndex mappings and common attributes

* Data Migration scripts to change the glossaryTerm and dataContract structure

* Update generated TypeScript types

* Fixed zh/spreadsheet index json error

* Fix Postgre migration script

* Changed the entityStatus.json to status.json
Removed the duplicates of entityStatus in the indexMapping
Modified the sample data to take in EntityStatus.Approved instead of ContractStatus.Active

* Update generated TypeScript types

* dummy commit

* Fix UI Build Issues with the New EntityStatus
Fix py tests

* Migrations for all the entities that need entityStatus

* Update generated TypeScript types

* Removed Post Migration scripts

* Fix UI  and py for entityStatus

* Update generated TypeScript types

* Fix: DataContractResourceTest

* Fix UI and py for importing entityStatus

* UI to show and fetch Reviewers

* cleanup

* Removed Overridden SetDefaultStatus in GlossaryTermRepository

* Removed unnecessary validation

* Added entityStatus in search_entity_index_mapping.json

* Fixed DataContractResourceTest

* mvn spotless apply and fix migration scripts

* fix tests

* fix type error

* fix advanced search tests

* Status comparison using enums and supportsStatus to supportsEntityStatus

* mvn spotless apply

* fix merge conflict

* update entity status

* fix tests

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Karan Hotchandani <33024356+karanh37@users.noreply.github.com>
Co-authored-by: karanh37 <karanh37@gmail.com>
2025-09-03 12:49:45 +05:30

271 lines
10 KiB
Python

# Copyright 2025 Collate
# Licensed under the Collate Community License, Version 1.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# https://github.com/open-metadata/OpenMetadata/blob/main/ingestion/LICENSE
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
OpenMetadata high-level API DataContract test
"""
import uuid
from datetime import datetime
from unittest import TestCase
from _openmetadata_testutils.ometa import OM_JWT
from metadata.generated.schema.api.data.createDatabase import CreateDatabaseRequest
from metadata.generated.schema.api.data.createDatabaseSchema import (
CreateDatabaseSchemaRequest,
)
from metadata.generated.schema.api.data.createDataContract import (
CreateDataContractRequest,
)
from metadata.generated.schema.api.data.createTable import CreateTableRequest
from metadata.generated.schema.api.services.createDatabaseService import (
CreateDatabaseServiceRequest,
)
from metadata.generated.schema.entity.data.dataContract import DataContract
from metadata.generated.schema.entity.data.table import Column, DataType, Table
from metadata.generated.schema.entity.datacontract.dataContractResult import (
DataContractResult,
)
from metadata.generated.schema.entity.datacontract.qualityValidation import (
QualityValidation,
)
from metadata.generated.schema.entity.datacontract.schemaValidation import (
SchemaValidation,
)
from metadata.generated.schema.entity.services.connections.database.common.basicAuth import (
BasicAuth,
)
from metadata.generated.schema.entity.services.connections.database.mysqlConnection import (
MysqlConnection,
)
from metadata.generated.schema.entity.services.connections.metadata.openMetadataConnection import (
OpenMetadataConnection,
)
from metadata.generated.schema.entity.services.databaseService import (
DatabaseConnection,
DatabaseService,
DatabaseServiceType,
)
from metadata.generated.schema.security.client.openMetadataJWTClientConfig import (
OpenMetadataJWTClientConfig,
)
from metadata.generated.schema.type.basic import EntityName, Uuid
from metadata.generated.schema.type.contractExecutionStatus import (
ContractExecutionStatus,
)
from metadata.generated.schema.type.entityReference import EntityReference
from metadata.generated.schema.type.status import EntityStatus
from metadata.ingestion.ometa.ometa_api import OpenMetadata
class OMetaDataContractTest(TestCase):
"""
Run this integration test with the local API available
Install the ingestion package before running the tests
"""
service_entity_id = None
server_config = OpenMetadataConnection(
hostPort="http://localhost:8585/api",
authProvider="openmetadata",
securityConfig=OpenMetadataJWTClientConfig(
jwtToken=OM_JWT,
),
)
metadata = OpenMetadata(server_config)
assert metadata.health_check()
service = CreateDatabaseServiceRequest(
name="test-service-data-contract",
serviceType=DatabaseServiceType.Mysql,
connection=DatabaseConnection(
config=MysqlConnection(
username="username",
authType=BasicAuth(password="password"),
hostPort="http://localhost:3306",
)
),
)
@classmethod
def setUpClass(cls) -> None:
"""
Prepare ingredients - create database service, database, schema, and table
"""
# Create Database Service
cls.service_entity = cls.metadata.create_or_update(data=cls.service)
# Create Database
create_db = CreateDatabaseRequest(
name="test-db-datacontract",
service=cls.service_entity.fullyQualifiedName,
)
cls.create_db_entity = cls.metadata.create_or_update(data=create_db)
# Create Database Schema
create_schema = CreateDatabaseSchemaRequest(
name="test-schema-datacontract",
database=cls.create_db_entity.fullyQualifiedName,
)
cls.create_schema_entity = cls.metadata.create_or_update(data=create_schema)
# Create Table
cls.table_entity: Table = cls.metadata.create_or_update(
CreateTableRequest(
name="test-table-datacontract",
databaseSchema=cls.create_schema_entity.fullyQualifiedName,
columns=[
Column(name="id", dataType=DataType.BIGINT),
Column(name="name", dataType=DataType.STRING),
],
)
)
cls.create_data_contract = CreateDataContractRequest(
name=EntityName(root="TestDataContract"),
description="Test data contract for validation",
entity=EntityReference(
id=cls.table_entity.id, type="table"
), # Will be set in setUpClass
entityStatus=EntityStatus.Draft,
schema=cls.table_entity.columns[:2],
)
@classmethod
def tearDownClass(cls) -> None:
"""
Clean up - delete service recursively to clean up all child entities
"""
service_id = str(
cls.metadata.get_by_name(
entity=DatabaseService, fqn=cls.service.name.root
).id.root
)
cls.metadata.delete(
entity=DatabaseService,
entity_id=service_id,
recursive=True,
hard_delete=True,
)
def test_create_data_contract(self):
"""
We can create a DataContract and we receive it back as Entity
"""
res: DataContract = self.metadata.create_or_update(
data=self.create_data_contract
)
self.assertEqual(res.name, self.create_data_contract.name)
self.assertEqual(res.description, self.create_data_contract.description)
self.assertEqual(res.entityStatus, self.create_data_contract.entityStatus)
self.assertEqual(res.entity.id, self.table_entity.id)
self.assertEqual(res.entity.type, "table")
self.assertEqual(len(res.schema_), 2)
def test_get_data_contract_by_name(self):
"""We can fetch DataContract by name"""
contract: DataContract = self.metadata.create_or_update(
data=self.create_data_contract
)
res: DataContract = self.metadata.get_by_name(
entity=DataContract, fqn=contract.fullyQualifiedName.root
)
self.assertEqual(res.name, self.create_data_contract.name)
self.assertEqual(res.description, self.create_data_contract.description)
self.assertEqual(res.entityStatus, self.create_data_contract.entityStatus)
def test_get_data_contract_by_id(self):
"""We can fetch DataContract by ID"""
created_contract: DataContract = self.metadata.create_or_update(
data=self.create_data_contract
)
res: DataContract = self.metadata.get_by_id(
entity=DataContract, entity_id=created_contract.id
)
self.assertEqual(res.name, self.create_data_contract.name)
self.assertEqual(res.id, created_contract.id)
self.assertEqual(res.entityStatus, self.create_data_contract.entityStatus)
def test_put_data_contract_result(self):
"""We can create and store DataContract execution results"""
# First create the data contract
created_contract: DataContract = self.metadata.create_or_update(
data=self.create_data_contract
)
# Create a data contract result
contract_result = DataContractResult(
id=Uuid(root=uuid.uuid4()),
dataContractFQN=created_contract.fullyQualifiedName,
timestamp=int(datetime.now().timestamp() * 1000),
contractExecutionStatus=ContractExecutionStatus.Success,
schemaValidation=SchemaValidation(
passed=2,
failed=0,
total=2,
),
qualityValidation=QualityValidation(
passed=0,
failed=0,
total=0,
),
)
# Store the result using the mixin method
result = self.metadata.put_data_contract_result(
created_contract.id, contract_result
)
self.assertIsNotNone(result)
self.assertEqual(
result.contractExecutionStatus, ContractExecutionStatus.Success
)
# Verify we can get the latest result
latest_result = self.metadata.get_latest_data_contract_result(
created_contract.id
)
self.assertIsNotNone(latest_result)
self.assertEqual(
latest_result.contractExecutionStatus, ContractExecutionStatus.Success
)
# Verify we can get all results
all_results = self.metadata.get_data_contract_results(created_contract.id)
self.assertIsNotNone(all_results)
self.assertGreaterEqual(len(all_results), 1)
def test_update_data_contract_status(self):
"""We can update DataContract status"""
# Create data contract in Draft status
created_contract: DataContract = self.metadata.create_or_update(
data=self.create_data_contract
)
self.assertEqual(created_contract.entityStatus, EntityStatus.Draft)
# Update to Active status
updated_request = CreateDataContractRequest(
name=self.create_data_contract.name,
description=self.create_data_contract.description,
entity=self.create_data_contract.entity,
entityStatus=EntityStatus.Approved,
schema=self.create_data_contract.schema_,
)
updated_contract: DataContract = self.metadata.create_or_update(
data=updated_request
)
self.assertEqual(updated_contract.entityStatus, EntityStatus.Approved)
self.assertEqual(updated_contract.id, created_contract.id)