Skip to content

Commit

Permalink
feat: framework for overriding standard test validation methods (#33)
Browse files Browse the repository at this point in the history
- implements a sample target test class to make assertions on the table
- assert row count, column count, and types
- drop the table after the test is done

I disabled the python version matrix in CI because they were interfering
with each other since right now we share a schema. Adding the github
action run ID as a prefix on the schema is probably a good idea
#34.

---------

Co-authored-by: Edgar R. M. <[email protected]>
Co-authored-by: Ken Payne <[email protected]>
  • Loading branch information
3 people authored Jun 5, 2023
1 parent 29f7d2c commit f144882
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 11 deletions.
40 changes: 29 additions & 11 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,49 @@
from __future__ import annotations

import os
from typing import Any, Dict
import uuid
from typing import Any

import pytest
from singer_sdk.testing import get_target_test_class
from singer_sdk.testing import TargetTestRunner, get_test_class

from target_snowflake.target import TargetSnowflake

SAMPLE_CONFIG: Dict[str, Any] = {
from .test_impl import target_tests

SAMPLE_CONFIG: dict[str, Any] = {
"user": os.environ["TARGET_SNOWFLAKE_USER"],
"password": os.environ["TARGET_SNOWFLAKE_PASSWORD"],
"account": os.environ["TARGET_SNOWFLAKE_ACCOUNT"],
"database": os.environ["TARGET_SNOWFLAKE_DATABASE"],
"warehouse": os.environ["TARGET_SNOWFLAKE_WAREHOUSE"],
"role": os.environ["TARGET_SNOWFLAKE_ROLE"],
"schema": "PUBLIC",
"default_target_schema": "TARGET_SNOWFLAKE",
"default_target_schema": f"TARGET_SNOWFLAKE_{uuid.uuid4().hex[0:6]!s}",
}


# Run standard built-in target tests from the SDK:
StandardTargetTests = get_target_test_class(
target_class=TargetSnowflake,
config=SAMPLE_CONFIG,
# Custom so I can implement all validate methods
StandardTargetTests = get_test_class(
test_runner=TargetTestRunner(
target_class=TargetSnowflake,
config=SAMPLE_CONFIG,
),
test_suites=[target_tests],
suite_config=None,
)


class TestTargetSnowflake(StandardTargetTests): # type: ignore[misc, valid-type] # noqa: E501
"""Standard Target Tests."""

@pytest.fixture(scope="class")
def resource(self): # noqa: ANN201
def connection(self, runner):
return runner.singer_class.default_sink_class.connector_class(
runner.config
).connection

@pytest.fixture(scope="class")
def resource(self, runner, connection): # noqa: ANN201
"""Generic external resource.
This fixture is useful for setup and teardown of external resources,
Expand All @@ -42,4 +54,10 @@ def resource(self): # noqa: ANN201
Example usage can be found in the SDK samples test suite:
https://github.com/meltano/sdk/tree/main/tests/samples
"""
return "resource"
connection.execute(
f"create schema {runner.config['database']}.{runner.config['default_target_schema']}"
)
yield
connection.execute(
f"drop schema if exists {runner.config['database']}.{runner.config['default_target_schema']}"
)
67 changes: 67 additions & 0 deletions tests/test_impl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import snowflake.sqlalchemy.custom_types as sct
from singer_sdk.testing.suites import TestSuite
from singer_sdk.testing.target_tests import (
TargetArrayData,
TargetCamelcaseComplexSchema,
TargetCamelcaseTest,
TargetCliPrintsTest,
TargetDuplicateRecords,
TargetEncodedStringData,
TargetInvalidSchemaTest,
TargetNoPrimaryKeys,
TargetOptionalAttributes,
TargetRecordBeforeSchemaTest,
TargetRecordMissingKeyProperty,
TargetSchemaNoProperties,
TargetSchemaUpdates,
TargetSpecialCharsInAttributes,
)


class SnowflakeTargetArrayData(TargetArrayData):
def validate(self) -> None:
connector = self.target.default_sink_class.connector_class(self.target.config)
table = f"{self.target.config['database']}.{self.target.config['default_target_schema']}.test_{self.name}".upper()
result = connector.connection.execute(
f"select * from {table}",
)
assert result.rowcount == 4
row = result.first()
assert len(row) == 8
assert row[1] == '[\n "apple",\n "orange",\n "pear"\n]'
table_schema = connector.get_table(table)
expected_types = {
"id": sct._CUSTOM_DECIMAL,
"fruits": sct.VARIANT,
"_sdc_extracted_at": sct.TIMESTAMP_NTZ,
"_sdc_batched_at": sct.TIMESTAMP_NTZ,
"_sdc_received_at": sct.TIMESTAMP_NTZ,
"_sdc_deleted_at": sct.TIMESTAMP_NTZ,
"_sdc_table_version": sct.NUMBER,
"_sdc_sequence": sct.NUMBER,
}
for column in table_schema.columns:
assert column.name in expected_types
isinstance(column.type, expected_types[column.name])


target_tests = TestSuite(
kind="target",
tests=[
SnowflakeTargetArrayData,
TargetCamelcaseComplexSchema,
TargetCamelcaseTest,
TargetCliPrintsTest,
TargetDuplicateRecords,
TargetEncodedStringData,
TargetInvalidSchemaTest,
# TargetMultipleStateMessages,
TargetNoPrimaryKeys,
TargetOptionalAttributes,
TargetRecordBeforeSchemaTest,
TargetRecordMissingKeyProperty,
TargetSchemaNoProperties,
TargetSchemaUpdates,
TargetSpecialCharsInAttributes,
],
)

0 comments on commit f144882

Please sign in to comment.