Files
wehub-resource-sync bf2343b7e4
Integration Tests - MySQL + Elasticsearch / Detect Changes (push) Has been cancelled
Integration Tests - MySQL + Elasticsearch / integration-tests-mysql-elasticsearch (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + Elasticsearch + Redis / integration-tests-postgres-elasticsearch-redis (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / Detect Changes (push) Has been cancelled
Integration Tests - PostgreSQL + OpenSearch / integration-tests-postgres-opensearch (push) Has been cancelled
Java Checkstyle / java-checkstyle (push) Has been cancelled
Maven Collate Tests / maven-collate-ci (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests-status (push) Has been cancelled
Publish Package to Maven Central Repository / publish-maven-packages (push) Has been cancelled
OpenMetadata Service Unit Tests / Detect Changes (push) Has been cancelled
OpenMetadata Service Unit Tests / openmetadata-service-unit-tests (push) Has been cancelled
OpenMetadata Service Unit Tests / k8s_operator-unit-tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:35:45 +08:00

289 lines
10 KiB
Python

# Copyright 2022 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.
"""
Test Exasol connector with CLI
"""
import subprocess
import time
from typing import List # noqa: UP035
import pytest
from sqlalchemy import text
from metadata.generated.schema.entity.data.table import Table
from metadata.ingestion.api.status import Status
from .base.e2e_types import E2EType # noqa: TID252
from .common.test_cli_db import CliCommonDB # noqa: TID252
from .common_e2e_sqa_mixins import SQACommonMethods # noqa: TID252
SERVICE_NAME = "local_exasol"
SCHEMA_NAME = "openmetadata_schema"
TABLE_NAME = "datatypes"
VIEW_NAME = f"view_{TABLE_NAME}"
TABLE_COMMENT = "OpenMetadata Exasol CLI table comment"
COLUMN_COMMENT = "OpenMetadata Exasol CLI column comment"
DB_PORT = 8563
# The compressed size of this image is 3.23 GB, so it takes on the order of minutes
# to pull it.
DB_VERSION = "2025.1.8"
CONTAINER_SUFFIX = "exasoaddl"
CONTAINER_NAME = f"db_container_{CONTAINER_SUFFIX}"
class ExasolCliTest(CliCommonDB.TestSuite, SQACommonMethods):
"""
Exasol CLI Tests
"""
create_table_query: str = f"""
CREATE TABLE IF NOT EXISTS {SCHEMA_NAME}.{TABLE_NAME} (
col_boolean BOOLEAN,
col_decimal DOUBLE PRECISION,
col_date DATE,
col_timestamp TIMESTAMP,
col_char CHAR(1),
col_varchar VARCHAR(1)
);
"""
comment_column_query: str = f"""
COMMENT ON COLUMN {SCHEMA_NAME}.{TABLE_NAME}.col_boolean IS '{COLUMN_COMMENT}';
"""
comment_table_query: str = f"""
COMMENT ON TABLE {SCHEMA_NAME}.{TABLE_NAME} IS '{TABLE_COMMENT}';
"""
create_view_query: str = f"""
CREATE VIEW {SCHEMA_NAME}.{VIEW_NAME} AS
SELECT
col_boolean,
col_decimal,
col_date,
col_timestamp,
col_char,
col_varchar
FROM {SCHEMA_NAME}.{TABLE_NAME}
"""
insert_data_queries: List[str] = [ # noqa: RUF012, UP006
f"""
INSERT INTO {SCHEMA_NAME}.{TABLE_NAME} (col_boolean, col_decimal, col_date, col_timestamp, col_char, col_varchar) VALUES
(TRUE, 18.5, '2023-07-13', '2023-07-13 06:04:45', 'a', 'b');
""",
f"""
INSERT INTO {SCHEMA_NAME}.{TABLE_NAME} (col_boolean, col_decimal, col_date, col_timestamp, col_char, col_varchar) VALUES
(TRUE, -18.5, '2023-09-13', '2023-09-13 06:04:45', 'c', 'd');
""",
]
drop_table_query: str = f"""
DROP TABLE IF EXISTS {SCHEMA_NAME}.{TABLE_NAME};
"""
drop_view_query: str = f"""
DROP VIEW IF EXISTS {SCHEMA_NAME}.{VIEW_NAME};
"""
@classmethod
def setUpClass(cls):
"""
To run the Exasol tests, we use the Integration Test Docker Environment (ITDE)
package. By default, this pulls an Exasol Database Docker image of the
requested version. However, to reduce confusion and make it clearer what is
leading to an issue, we have added to the setup that the Docker image is pulled
first and in a separate command.
The ITDE includes configuration files for each Exasol Database Docker image.
Thus, there is unfortunately a tight coupling between the ITDE version
you are using and the Docker image you can use. Over time, Exasol may drop
support of certain Docker images, like the one used in this test, if the
tests break and assistance is needed due to that, please reach out to us at
opensource@exasol.com or open an issue in the ITDE at
https://github.com/exasol/integration-test-docker-environment.
For example, a mismatch in ITDE and Docker image would lead to an error like
this when the "itde spawn-test-environment" were run:
FileNotFoundError: [Errno 2] No such file or directory:
'$HOME/OpenMetadata/venv/lib/python3.11/site-packages/exasol_integration_test_docker_environment/docker_db_config/2025.2.1/init_db.sh'
"""
subprocess.run(["docker", "pull", f"exasol/docker-db:{DB_VERSION}"], check=True)
subprocess.run(
[
"itde",
"spawn-test-environment",
"--environment-name",
CONTAINER_SUFFIX,
"--database-port-forward",
f"{DB_PORT}",
"--bucketfs-port-forward",
"2580",
"--docker-db-image-version",
DB_VERSION,
"--db-mem-size",
"4GB",
],
check=True,
)
super().setUpClass()
with cls.engine.connect() as connection:
connection.execute(text(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA_NAME}"))
connection.execute(text("CREATE SCHEMA IF NOT EXISTS IGNORE_SCHEMA"))
connection.execute(text(cls.create_table_query))
connection.execute(text(cls.comment_column_query))
connection.execute(text(cls.comment_table_query))
connection.execute(
text(f"CREATE OR REPLACE TABLE {SCHEMA_NAME}.IGNORE_TABLE AS SELECT * FROM {SCHEMA_NAME}.{TABLE_NAME}")
)
connection.commit()
@classmethod
def tearDownClass(cls):
super().tearDownClass()
subprocess.run(["docker", "kill", CONTAINER_NAME], check=True, encoding="utf-8")
@pytest.mark.order(8)
def test_table_filter_excludes(self) -> None:
"""7. Vanilla ingestion + exclude table filter pattern
We will perform the following steps:
1. build config file for ingest with filters
2. run ingest `self.run_command()` defaults to `ingestion`
"""
self.build_config_file(E2EType.INGEST_DB_FILTER_TABLE, {"excludes": self.get_excludes_tables()})
result = self.run_command()
sink_status, source_status = self.retrieve_statuses(result)
self.assert_filtered_tables_excludes(source_status, sink_status)
def assert_for_table_with_profiler(self, source_status: Status, sink_status: Status) -> None:
super().assert_for_table_with_profiler(source_status, sink_status)
profile = self.retrieve_profile(self.fqn_created_table())
self.assertIsNotNone(profile.profile)
self.assertEqual(profile.profile.rowCount, 2.0)
def assert_table_metadata(self) -> None:
table = None
for _ in range(6):
tables = self.openmetadata.list_entities(
entity=Table,
fields=["*"],
params={"database": f"{SERVICE_NAME}.default"},
).entities
table = next(
(
candidate
for candidate in tables
if candidate.name.root.lower() == TABLE_NAME.lower()
and candidate.databaseSchema
and candidate.databaseSchema.name
and candidate.databaseSchema.name.lower() == SCHEMA_NAME.lower()
),
None,
)
if table is not None:
break
time.sleep(5)
self.assertIsNotNone(table)
self.assertEqual(table.description.root, TABLE_COMMENT)
column_names = [column.name.root for column in table.columns]
self.assertEqual(
column_names,
[
"col_boolean",
"col_decimal",
"col_date",
"col_timestamp",
"col_char",
"col_varchar",
],
)
column_by_name = {column.name.root: column for column in table.columns}
self.assertEqual(column_by_name["col_boolean"].description.root, COLUMN_COMMENT)
self.assertIsNone(column_by_name["col_date"].description)
@pytest.mark.order(100)
def test_table_metadata_contains_comments(self) -> None:
self.build_config_file(E2EType.INGEST_DB_FILTER_TABLE, {"excludes": self.get_excludes_tables()})
result = self.run_command()
sink_status, source_status = self.retrieve_statuses(result)
self.assert_filtered_tables_excludes(source_status, sink_status)
self.assert_table_metadata()
@staticmethod
def get_connector_name() -> str:
return "exasol"
def create_table_and_view(self) -> None:
SQACommonMethods.create_table_and_view(self)
def delete_table_and_view(self) -> None:
SQACommonMethods.delete_table_and_view(self)
@staticmethod
def get_includes_schemas() -> List[str]: # noqa: UP006
return [f"{SCHEMA_NAME}.*"]
@classmethod
def get_excludes_schemas(cls) -> List[str]: # noqa: UP006
return ["IGNORE_SCHEMA.*"]
@staticmethod
def get_includes_tables() -> List[str]: # noqa: UP006
return [f"{TABLE_NAME}"]
@staticmethod
def get_excludes_tables() -> List[str]: # noqa: UP006
return ["IGNORE_TABLE"]
@staticmethod
def expected_tables() -> int:
return 1
def expected_sample_size(self) -> int:
return len(self.insert_data_queries)
def view_column_lineage_count(self) -> int:
return 6
def expected_lineage_node(self) -> str:
return f"{SERVICE_NAME}.default.{SCHEMA_NAME}.{VIEW_NAME}"
@staticmethod
def fqn_created_table() -> str:
return f"{SERVICE_NAME}.default.{SCHEMA_NAME}.{TABLE_NAME}"
@staticmethod
def expected_filtered_schema_includes() -> int:
return 1
@staticmethod
def expected_filtered_schema_excludes() -> int:
return 1
@staticmethod
def expected_filtered_table_includes() -> int:
return 1
@staticmethod
def expected_filtered_table_excludes() -> int:
return 1
@staticmethod
def expected_filtered_mix() -> int:
return 2