Пример #1
0
class TestSource:
    @pytest.mark.parametrize(
        "state",
        [None, resources.ResourceState("config_path", "workspace_id", "resource_id", 123, "abc")],
    )
    def test_init(self, mocker, mock_api_client, local_configuration, state):
        assert resources.Source.__base__ == resources.SourceAndDestination
        mocker.patch.object(resources.Source, "resource_id", "foo")
        source = resources.Source(mock_api_client, "workspace_id", local_configuration, "bar.yaml")
        mocker.patch.object(source, "state", state)
        assert source.api == resources.source_api.SourceApi
        assert source.create_function_name == "create_source"
        assert source.resource_id_field == "source_id"
        assert source.update_function_name == "update_source"
        assert source.resource_type == "source"
        assert source.APPLY_PRIORITY == 0
        assert source.create_payload == resources.SourceCreate(
            source.definition_id, source.configuration, source.workspace_id, source.resource_name
        )
        assert source.update_payload == resources.SourceUpdate(
            source_id=source.resource_id, connection_configuration=source.configuration, name=source.resource_name
        )
        if state is None:
            assert source.get_payload is None
        else:
            assert source.get_payload == resources.SourceIdRequestBody(state.resource_id)

    @pytest.mark.parametrize(
        "resource_id",
        [None, "foo"],
    )
    def test_source_discover_schema_request_body(self, mocker, mock_api_client, resource_id, local_configuration):
        mocker.patch.object(resources, "SourceDiscoverSchemaRequestBody")
        mocker.patch.object(resources.Source, "resource_id", resource_id)
        source = resources.Source(mock_api_client, "workspace_id", local_configuration, "bar.yaml")
        if resource_id is None:
            with pytest.raises(resources.NonExistingResourceError):
                source.source_discover_schema_request_body
                resources.SourceDiscoverSchemaRequestBody.assert_not_called()
        else:
            assert source.source_discover_schema_request_body == resources.SourceDiscoverSchemaRequestBody.return_value
            resources.SourceDiscoverSchemaRequestBody.assert_called_with(source.resource_id)

    def test_catalog(self, mocker, mock_api_client, local_configuration):
        mocker.patch.object(resources.Source, "source_discover_schema_request_body")
        source = resources.Source(mock_api_client, "workspace_id", local_configuration, "bar.yaml")
        source.api_instance = mocker.Mock()
        catalog = source.catalog
        assert catalog == source.api_instance.discover_schema_for_source.return_value.catalog
        source.api_instance.discover_schema_for_source.assert_called_with(source.source_discover_schema_request_body)

    def test_definition(self, mocker, mock_api_client, local_configuration):
        mocker.patch.object(resources.source_definition_specification_api, "SourceDefinitionSpecificationApi")
        mock_api_instance = resources.source_definition_specification_api.SourceDefinitionSpecificationApi.return_value
        source = resources.Source(mock_api_client, "workspace_id", local_configuration, "bar.yaml")
        assert source.definition == mock_api_instance.get_source_definition_specification.return_value
        resources.source_definition_specification_api.SourceDefinitionSpecificationApi.assert_called_with(mock_api_client)
        expected_payload = SourceDefinitionIdWithWorkspaceId(source_definition_id=source.definition_id, workspace_id=source.workspace_id)
        mock_api_instance.get_source_definition_specification.assert_called_with(expected_payload)
Пример #2
0
 def test_init(self, mocker):
     mocker.patch.object(resources, "os")
     state = resources.ResourceState("config_path", "workspace_id", "resource_id", 123, "config_hash")
     assert state.configuration_path == "config_path"
     assert state.workspace_id == "workspace_id"
     assert state.resource_id == "resource_id"
     assert state.generation_timestamp == 123
     assert state.configuration_hash == "config_hash"
     assert state.path == resources.os.path.join.return_value
     resources.os.path.dirname.assert_called_with("config_path")
     resources.os.path.join.assert_called_with(resources.os.path.dirname.return_value, "state_workspace_id.yaml")
Пример #3
0
class TestDestination:
    @pytest.mark.parametrize(
        "state",
        [None, resources.ResourceState("config_path", "workspace_id", "resource_id", 123, "abc")],
    )
    def test_init(self, mocker, mock_api_client, local_configuration, state):
        assert resources.Destination.__base__ == resources.SourceAndDestination
        mocker.patch.object(resources.Destination, "resource_id", "foo")
        destination = resources.Destination(mock_api_client, "workspace_id", local_configuration, "bar.yaml")
        mocker.patch.object(destination, "state", state)
        assert destination.api == resources.destination_api.DestinationApi
        assert destination.create_function_name == "create_destination"
        assert destination.resource_id_field == "destination_id"
        assert destination.update_function_name == "update_destination"
        assert destination.resource_type == "destination"
        assert destination.APPLY_PRIORITY == 0
        assert destination.create_payload == resources.DestinationCreate(
            destination.workspace_id, destination.resource_name, destination.definition_id, destination.configuration
        )
        assert destination.update_payload == resources.DestinationUpdate(
            destination_id=destination.resource_id, connection_configuration=destination.configuration, name=destination.resource_name
        )
        if state is None:
            assert destination.get_payload is None
        else:
            assert destination.get_payload == resources.DestinationIdRequestBody(state.resource_id)

    def test_definition(self, mocker, mock_api_client, local_configuration):
        mocker.patch.object(resources.destination_definition_specification_api, "DestinationDefinitionSpecificationApi")
        mock_api_instance = resources.destination_definition_specification_api.DestinationDefinitionSpecificationApi.return_value
        destination = resources.Destination(mock_api_client, "workspace_id", local_configuration, "bar.yaml")
        assert destination.definition == mock_api_instance.get_destination_definition_specification.return_value
        resources.destination_definition_specification_api.DestinationDefinitionSpecificationApi.assert_called_with(mock_api_client)
        expected_payload = DestinationDefinitionIdWithWorkspaceId(
            destination_definition_id=destination.definition_id, workspace_id=destination.workspace_id
        )
        mock_api_instance.get_destination_definition_specification.assert_called_with(expected_payload)
Пример #4
0
class TestConnection:
    @pytest.fixture
    def connection_configuration(self):
        return {
            "definition_type": "connection",
            "resource_name": "my_connection",
            "source_configuration_path": "my_source_configuration_path",
            "destination_configuration_path": "my_destination_configuration_path",
            "configuration": {
                "namespace_definition": "customformat",
                "namespace_format": "foo",
                "prefix": "foo",
                "sync_catalog": {
                    "streams": [
                        {
                            "stream": {
                                "name": "name_example",
                                "json_schema": {},
                                "supported_sync_modes": ["incremental"],
                                "source_defined_cursor": True,
                                "default_cursor_field": ["default_cursor_field"],
                                "source_defined_primary_key": [["string_example"]],
                                "namespace": "namespace_example",
                            },
                            "config": {
                                "sync_mode": "incremental",
                                "cursor_field": ["cursor_field_example"],
                                "destination_sync_mode": "append_dedup",
                                "primary_key": [["string_example"]],
                                "alias_name": "alias_name_example",
                                "selected": True,
                            },
                        }
                    ]
                },
                "schedule": {"units": 1, "time_unit": "days"},
                "status": "active",
                "resource_requirements": {"cpu_request": "foo", "cpu_limit": "foo", "memory_request": "foo", "memory_limit": "foo"},
            },
        }

    @pytest.fixture
    def connection_configuration_with_normalization(self, connection_configuration):
        connection_configuration_with_normalization = deepcopy(connection_configuration)
        connection_configuration_with_normalization["configuration"]["operations"] = [
            {"name": "Normalization", "operator_configuration": {"normalization": {"option": "basic"}, "operator_type": "normalization"}}
        ]
        return connection_configuration_with_normalization

    @pytest.fixture
    def legacy_connection_configurations(self):
        return [
            {
                "definition_type": "connection",
                "resource_name": "my_connection",
                "source_id": "my_source",
                "destination_id": "my_destination",
                "configuration": {
                    "namespaceDefinition": "customformat",
                    "namespaceFormat": "foo",
                    "prefix": "foo",
                    "syncCatalog": {
                        "streams": [
                            {
                                "stream": {
                                    "name": "name_example",
                                    "json_schema": {},
                                    "supported_sync_modes": ["incremental"],
                                    "source_defined_cursor": True,
                                    "default_cursor_field": ["default_cursor_field"],
                                    "source_defined_primary_key": [["string_example"]],
                                    "namespace": "namespace_example",
                                },
                                "config": {
                                    "sync_mode": "incremental",
                                    "cursor_field": ["cursor_field_example"],
                                    "destination_sync_mode": "append_dedup",
                                    "primary_key": [["string_example"]],
                                    "alias_name": "alias_name_example",
                                    "selected": True,
                                },
                            }
                        ]
                    },
                    "schedule": {"units": 1, "time_unit": "days"},
                    "status": "active",
                    "resourceRequirements": {"cpu_request": "foo", "cpu_limit": "foo", "memory_request": "foo", "memory_limit": "foo"},
                },
            },
            {
                "definition_type": "connection",
                "resource_name": "my_connection",
                "source_id": "my_source",
                "destination_id": "my_destination",
                "configuration": {
                    "namespace_definition": "customformat",
                    "namespace_format": "foo",
                    "prefix": "foo",
                    "sync_catalog": {
                        "streams": [
                            {
                                "stream": {
                                    "name": "name_example",
                                    "jsonSchema": {},
                                    "supportedSyncModes": ["incremental"],
                                    "sourceDefinedCursor": True,
                                    "defaultCursorField": ["default_cursor_field"],
                                    "sourceDefinedPrimary_key": [["string_example"]],
                                    "namespace": "namespace_example",
                                },
                                "config": {
                                    "syncMode": "incremental",
                                    "cursorField": ["cursor_field_example"],
                                    "destinationSyncMode": "append_dedup",
                                    "primaryKey": [["string_example"]],
                                    "aliasName": "alias_name_example",
                                    "selected": True,
                                },
                            }
                        ]
                    },
                    "schedule": {"units": 1, "time_unit": "days"},
                    "status": "active",
                    "resource_requirements": {"cpu_request": "foo", "cpu_limit": "foo", "memory_request": "foo", "memory_limit": "foo"},
                },
            },
        ]

    @pytest.mark.parametrize(
        "state",
        [None, resources.ResourceState("config_path", "workspace_id", "resource_id", 123, "abc")],
    )
    def test_init(self, mocker, mock_api_client, state, connection_configuration):
        assert resources.Connection.__base__ == resources.BaseResource
        mocker.patch.object(resources.Connection, "resource_id", "foo")
        connection = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        mocker.patch.object(connection, "state", state)
        assert connection.api == resources.web_backend_api.WebBackendApi
        assert connection.create_function_name == "web_backend_create_connection"
        assert connection.update_function_name == "web_backend_update_connection"
        assert connection.resource_id_field == "connection_id"
        assert connection.resource_type == "connection"
        assert connection.APPLY_PRIORITY == 1

        assert connection.update_payload == resources.WebBackendConnectionUpdate(
            connection_id=connection.resource_id, **connection.configuration
        )
        if state is None:
            assert connection.get_payload is None
        else:
            assert connection.get_payload == resources.WebBackendConnectionRequestBody(
                connection_id=state.resource_id, with_refreshed_catalog=False
            )

    @pytest.mark.parametrize("file_not_found_error", [False, True])
    def test_source_id(self, mocker, mock_api_client, connection_configuration, file_not_found_error):
        assert resources.Connection.__base__ == resources.BaseResource
        mocker.patch.object(resources.Connection, "resource_id", "foo")
        if file_not_found_error:
            mocker.patch.object(
                resources.ResourceState, "from_configuration_path_and_workspace", mocker.Mock(side_effect=FileNotFoundError())
            )
        else:
            mocker.patch.object(
                resources.ResourceState,
                "from_configuration_path_and_workspace",
                mocker.Mock(return_value=mocker.Mock(resource_id="expected_source_id")),
            )

        connection = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        if file_not_found_error:
            with pytest.raises(resources.MissingStateError):
                connection.source_id
        else:
            source_id = connection.source_id
            assert source_id == "expected_source_id"
        resources.ResourceState.from_configuration_path_and_workspace.assert_called_with(
            connection_configuration["source_configuration_path"], connection.workspace_id
        )

    @pytest.mark.parametrize("file_not_found_error", [False, True])
    def test_destination_id(self, mocker, mock_api_client, connection_configuration, file_not_found_error):
        assert resources.Connection.__base__ == resources.BaseResource
        mocker.patch.object(resources.Connection, "resource_id", "foo")
        if file_not_found_error:
            mocker.patch.object(
                resources.ResourceState, "from_configuration_path_and_workspace", mocker.Mock(side_effect=FileNotFoundError())
            )
        else:
            mocker.patch.object(
                resources.ResourceState,
                "from_configuration_path_and_workspace",
                mocker.Mock(return_value=mocker.Mock(resource_id="expected_destination_id")),
            )

        connection = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        if file_not_found_error:
            with pytest.raises(resources.MissingStateError):
                connection.destination_id
        else:
            destination_id = connection.destination_id
            assert destination_id == "expected_destination_id"
        resources.ResourceState.from_configuration_path_and_workspace.assert_called_with(
            connection_configuration["destination_configuration_path"], connection.workspace_id
        )

    def test_create_payload_no_normalization(self, mocker, mock_api_client, connection_configuration):
        assert resources.Connection.__base__ == resources.BaseResource
        mocker.patch.object(resources.Connection, "resource_id", "foo")
        mocker.patch.object(resources.Connection, "source_id", "source_id")
        mocker.patch.object(resources.Connection, "destination_id", "destination_id")
        connection = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        assert connection.create_payload == resources.WebBackendConnectionCreate(
            name=connection.resource_name,
            source_id=connection.source_id,
            destination_id=connection.destination_id,
            **connection.configuration,
        )
        assert "operations" not in connection.create_payload

    def test_create_payload_with_normalization(self, mocker, mock_api_client, connection_configuration_with_normalization):
        assert resources.Connection.__base__ == resources.BaseResource
        mocker.patch.object(resources.Connection, "resource_id", "foo")
        mocker.patch.object(resources.Connection, "source_id", "source_id")
        mocker.patch.object(resources.Connection, "destination_id", "destination_id")
        connection = resources.Connection(mock_api_client, "workspace_id", connection_configuration_with_normalization, "bar.yaml")
        assert connection.create_payload == resources.WebBackendConnectionCreate(
            name=connection.resource_name,
            source_id=connection.source_id,
            destination_id=connection.destination_id,
            **connection.configuration,
        )
        assert isinstance(connection.create_payload["operations"][0], OperationCreate)

    def test_update_payload_no_normalization(self, mocker, mock_api_client, connection_configuration):
        assert resources.Connection.__base__ == resources.BaseResource
        mocker.patch.object(resources.Connection, "resource_id", "foo")
        mocker.patch.object(resources.Connection, "source_id", "source_id")
        mocker.patch.object(resources.Connection, "destination_id", "destination_id")
        connection = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        assert connection.update_payload == resources.WebBackendConnectionUpdate(
            connection_id=connection.resource_id,
            **connection.configuration,
        )
        assert "operations" not in connection.update_payload

    def test_update_payload_with_normalization(self, mocker, mock_api_client, connection_configuration_with_normalization):
        assert resources.Connection.__base__ == resources.BaseResource
        mocker.patch.object(resources.Connection, "resource_id", "foo")
        mocker.patch.object(resources.Connection, "source_id", "source_id")
        mocker.patch.object(resources.Connection, "destination_id", "destination_id")
        connection = resources.Connection(mock_api_client, "workspace_id", connection_configuration_with_normalization, "bar.yaml")
        assert connection.update_payload == resources.WebBackendConnectionUpdate(
            connection_id=connection.resource_id,
            **connection.configuration,
        )
        assert isinstance(connection.update_payload["operations"][0], WebBackendOperationCreateOrUpdate)

    @pytest.mark.parametrize(
        "remote_resource",
        [
            {
                "name": "foo",
                "source_id": "bar",
                "destination_id": "fooo",
                "connection_id": "baar",
                "operation_ids": "foooo",
                "foo": "bar",
            },
            {
                "name": "foo",
                "source_id": "bar",
                "destination_id": "fooo",
                "connection_id": "baar",
                "operation_ids": "foooo",
                "foo": "bar",
                "operations": [],
            },
            {
                "name": "foo",
                "source_id": "bar",
                "destination_id": "fooo",
                "connection_id": "baar",
                "operation_ids": "foooo",
                "foo": "bar",
                "operations": [{"workspace_id": "foo", "operation_id": "foo", "operator_configuration": {"normalization": "foo"}}],
            },
            {
                "name": "foo",
                "source_id": "bar",
                "destination_id": "fooo",
                "connection_id": "baar",
                "operation_ids": "foooo",
                "foo": "bar",
                "operations": [{"workspace_id": "foo", "operation_id": "foo", "operator_configuration": {"dbt": "foo"}}],
            },
        ],
    )
    def test_get_remote_comparable_configuration(self, mocker, mock_api_client, connection_configuration, remote_resource):
        mocker.patch.object(
            resources.Connection,
            "remote_resource",
            mocker.Mock(to_dict=mocker.Mock(return_value=remote_resource)),
        )
        resource = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        comparable = resource._get_remote_comparable_configuration()
        resource.remote_resource.to_dict.assert_called_once()

        assert isinstance(comparable, dict)
        assert all([k not in comparable for k in resource.remote_root_level_keys_to_filter_out_for_comparison])
        if "operations" in remote_resource and "operations" in comparable:
            assert all([k not in comparable["operations"][0] for k in resource.remote_operation_level_keys_to_filter_out])
            if remote_resource["operations"][0]["operator_configuration"].get("normalization") is not None:
                assert "dbt" not in remote_resource["operations"][0]["operator_configuration"]
            if remote_resource["operations"][0]["operator_configuration"].get("dbt") is not None:
                assert "normalization" not in remote_resource["operations"][0]["operator_configuration"]
        if "operations" in remote_resource and len(remote_resource["operations"]) == 0:
            assert "operations" not in comparable

    def test_create(self, mocker, mock_api_client, connection_configuration):
        mocker.patch.object(resources.Connection, "_create_or_update")
        mocker.patch.object(resources.Connection, "source_id", "source_id")
        mocker.patch.object(resources.Connection, "destination_id", "destination_id")
        resource = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        create_result = resource.create()
        assert create_result == resource._create_or_update.return_value
        resource._create_or_update.assert_called_with(resource._create_fn, resource.create_payload)

    def test_update(self, mocker, mock_api_client, connection_configuration):
        mocker.patch.object(resources.Connection, "_create_or_update")
        resource = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        resource.state = mocker.Mock(resource_id="foo")
        update_result = resource.update()
        assert update_result == resource._create_or_update.return_value
        resource._create_or_update.assert_called_with(resource._update_fn, resource.update_payload)

    def test__deserialize_raw_configuration(self, mock_api_client, connection_configuration):
        resource = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        configuration = resource._deserialize_raw_configuration()
        assert isinstance(configuration["sync_catalog"], AirbyteCatalog)
        assert configuration["namespace_definition"] == NamespaceDefinitionType(
            connection_configuration["configuration"]["namespace_definition"]
        )
        assert configuration["schedule"] == ConnectionSchedule(**connection_configuration["configuration"]["schedule"])
        assert configuration["resource_requirements"] == ResourceRequirements(
            **connection_configuration["configuration"]["resource_requirements"]
        )
        assert configuration["status"] == ConnectionStatus(connection_configuration["configuration"]["status"])
        assert list(configuration.keys()) == [
            "namespace_definition",
            "namespace_format",
            "prefix",
            "sync_catalog",
            "schedule",
            "status",
            "resource_requirements",
        ]

    def test__deserialize_operations(self, mock_api_client, connection_configuration):
        resource = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        operations = [
            {
                "operator_configuration": {"operator_type": "normalization", "normalization": {"option": "basic"}},
                "name": "operation-with-normalization",
            },
            {
                "operator_configuration": {
                    "operator_type": "dbt",
                    "dbt": {
                        "dbt_arguments": "run",
                        "docker_image": "fishtownanalytics/dbt:0.19.1",
                        "git_repo_branch": "my-branch-name",
                        "git_repo_url": "https://github.com/airbytehq/airbyte",
                    },
                },
                "name": "operation-with-custom_dbt",
            },
        ]
        deserialized_operations = resource._deserialize_operations(operations, OperationCreate)
        assert len(deserialized_operations) == 2
        assert all([isinstance(o, OperationCreate) for o in deserialized_operations])
        assert "normalization" in deserialized_operations[0]["operator_configuration"] and deserialized_operations[0][
            "operator_configuration"
        ]["operator_type"] == OperatorType("normalization")
        assert "dbt" in deserialized_operations[1]["operator_configuration"]
        assert deserialized_operations[1]["operator_configuration"]["operator_type"] == OperatorType("dbt")

        with pytest.raises(ValueError):
            resource._deserialize_operations(
                [
                    {
                        "operator_configuration": {"operator_type": "not-supported", "normalization": {"option": "basic"}},
                        "name": "operation-not-supported",
                    },
                ],
                OperationCreate,
            )

    def test__create_configured_catalog(self, mock_api_client, connection_configuration):
        resource = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        created_catalog = resource._create_configured_catalog(connection_configuration["configuration"]["sync_catalog"])
        stream, config = (
            connection_configuration["configuration"]["sync_catalog"]["streams"][0]["stream"],
            connection_configuration["configuration"]["sync_catalog"]["streams"][0]["config"],
        )

        assert len(created_catalog.streams) == len(connection_configuration["configuration"]["sync_catalog"]["streams"])
        assert created_catalog.streams[0].stream.name == stream["name"]
        assert created_catalog.streams[0].stream.json_schema == stream["json_schema"]
        assert created_catalog.streams[0].stream.supported_sync_modes == stream["supported_sync_modes"]
        assert created_catalog.streams[0].stream.source_defined_cursor == stream["source_defined_cursor"]
        assert created_catalog.streams[0].stream.namespace == stream["namespace"]
        assert created_catalog.streams[0].stream.source_defined_primary_key == stream["source_defined_primary_key"]
        assert created_catalog.streams[0].stream.default_cursor_field == stream["default_cursor_field"]

        assert created_catalog.streams[0].config.sync_mode == config["sync_mode"]
        assert created_catalog.streams[0].config.cursor_field == config["cursor_field"]
        assert created_catalog.streams[0].config.destination_sync_mode == config["destination_sync_mode"]
        assert created_catalog.streams[0].config.primary_key == config["primary_key"]
        assert created_catalog.streams[0].config.alias_name == config["alias_name"]
        assert created_catalog.streams[0].config.selected == config["selected"]

    def test__check_for_legacy_connection_configuration_keys(
        self, mock_api_client, connection_configuration, legacy_connection_configurations
    ):
        resource = resources.Connection(mock_api_client, "workspace_id", connection_configuration, "bar.yaml")
        assert resource._check_for_legacy_connection_configuration_keys(connection_configuration["configuration"]) is None
        for legacy_configuration in legacy_connection_configurations:
            with pytest.raises(resources.InvalidConfigurationError):
                resource._check_for_legacy_connection_configuration_keys(legacy_configuration["configuration"])
Пример #5
0
 def state(self):
     return resources.ResourceState("config_path", "workspace_id", "resource_id", 123, "config_hash")