# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# 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.
from __future__ import annotations

import json
import logging
from unittest import mock

import pytest
from requests.exceptions import HTTPError

from airflow.providers.google.cloud.hooks.datafusion import DataFusionAsyncHook, DataFusionHook
from airflow.providers.google.cloud.utils.datafusion import DataFusionPipelineType

from tests_common.test_utils.aiohttp import MockAiohttpClientResponse
from unit.google.cloud.utils.base_gcp_mock import mock_base_gcp_hook_default_project_id

API_VERSION = "v1beta1"
GCP_CONN_ID = "google_cloud_default"
HOOK_STR = "airflow.providers.google.cloud.hooks.datafusion.{}"
LOCATION = "test-location"
INSTANCE_NAME = "airflow-test-instance"
INSTANCE_URL = "http://datafusion.instance.com"
INSTANCE = {"type": "BASIC", "displayName": INSTANCE_NAME}
NAMESPACE = "default"
PROJECT_ID = "test_project_id"
PIPELINE_NAME = "shrubberyPipeline"
PIPELINE_ID = "123"
PIPELINE = {"test": "pipeline"}
RUNTIME_ARGS = {"arg1": "a", "arg2": "b"}
CONSTRUCTED_PIPELINE_URL = (
    f"{INSTANCE_URL}/v3/namespaces/{NAMESPACE}/apps/{PIPELINE_NAME}"
    f"/workflows/DataPipelineWorkflow/runs/{PIPELINE_ID}"
)
CONSTRUCTED_PIPELINE_URL_GET = (
    f"https://{INSTANCE_NAME}-{PROJECT_ID}-dot-eun1.datafusion."
    f"googleusercontent.com/api/v3/namespaces/{NAMESPACE}/apps/{PIPELINE_NAME}"
    f"/workflows/DataPipelineWorkflow/runs/{PIPELINE_ID}"
)
CONSTRUCTED_PIPELINE_STREAM_URL_GET = (
    f"https://{INSTANCE_NAME}-{PROJECT_ID}-dot-eun1.datafusion."
    f"googleusercontent.com/api/v3/namespaces/{NAMESPACE}/apps/{PIPELINE_NAME}"
    f"/apsrkss/DataStreamsSparkStreaming/runs/{PIPELINE_ID}"
)


class MockResponse:
    def __init__(self, status, data=None):
        self.status = status
        self.data = data


@pytest.fixture
def hook():
    with mock.patch(
        "airflow.providers.google.common.hooks.base_google.GoogleBaseHook.__init__",
        new=mock_base_gcp_hook_default_project_id,
    ):
        yield DataFusionHook(gcp_conn_id=GCP_CONN_ID)


@pytest.fixture
def hook_async():
    with mock.patch(
        "airflow.providers.google.common.hooks.base_google.GoogleBaseAsyncHook.__init__",
        new=mock_base_gcp_hook_default_project_id,
    ):
        yield DataFusionAsyncHook()


def session():
    return mock.Mock()


class TestDataFusionHook:
    @staticmethod
    def mock_endpoint(get_conn_mock):
        return get_conn_mock.return_value.projects.return_value.locations.return_value.instances.return_value

    def test_name(self, hook):
        expected = f"projects/{PROJECT_ID}/locations/{LOCATION}/instances/{INSTANCE_NAME}"
        assert hook._name(PROJECT_ID, LOCATION, INSTANCE_NAME) == expected

    def test_parent(self, hook):
        expected = f"projects/{PROJECT_ID}/locations/{LOCATION}"
        assert hook._parent(PROJECT_ID, LOCATION) == expected

    @mock.patch(HOOK_STR.format("build"))
    @mock.patch(HOOK_STR.format("DataFusionHook.get_client_options"))
    @mock.patch(HOOK_STR.format("DataFusionHook._authorize"))
    def test_get_conn(self, mock_authorize, mock_get_client_options, mock_build, hook):
        mock_authorize.return_value = "test"
        hook.get_conn()
        mock_build.assert_called_once_with(
            "datafusion",
            hook.api_version,
            http="test",
            cache_discovery=False,
            client_options=mock_get_client_options.return_value,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook.get_conn"))
    def test_restart_instance(self, get_conn_mock, hook):
        method_mock = self.mock_endpoint(get_conn_mock).restart
        method_mock.return_value.execute.return_value = "value"
        result = hook.restart_instance(instance_name=INSTANCE_NAME, location=LOCATION, project_id=PROJECT_ID)

        assert result == "value"
        method_mock.assert_called_once_with(name=hook._name(PROJECT_ID, LOCATION, INSTANCE_NAME))

    @mock.patch(HOOK_STR.format("DataFusionHook.get_conn"))
    def test_delete_instance(self, get_conn_mock, hook):
        method_mock = self.mock_endpoint(get_conn_mock).delete
        method_mock.return_value.execute.return_value = "value"
        result = hook.delete_instance(instance_name=INSTANCE_NAME, location=LOCATION, project_id=PROJECT_ID)

        assert result == "value"
        method_mock.assert_called_once_with(name=hook._name(PROJECT_ID, LOCATION, INSTANCE_NAME))

    @mock.patch(HOOK_STR.format("DataFusionHook.get_conn"))
    def test_create_instance(self, get_conn_mock, hook):
        method_mock = self.mock_endpoint(get_conn_mock).create
        method_mock.return_value.execute.return_value = "value"
        result = hook.create_instance(
            instance_name=INSTANCE_NAME,
            instance=INSTANCE,
            location=LOCATION,
            project_id=PROJECT_ID,
        )

        assert result == "value"
        method_mock.assert_called_once_with(
            parent=hook._parent(PROJECT_ID, LOCATION),
            body=INSTANCE,
            instanceId=INSTANCE_NAME,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook.get_conn"))
    def test_patch_instance(self, get_conn_mock, hook):
        method_mock = self.mock_endpoint(get_conn_mock).patch
        method_mock.return_value.execute.return_value = "value"
        result = hook.patch_instance(
            instance_name=INSTANCE_NAME,
            instance=INSTANCE,
            update_mask="instance.name",
            location=LOCATION,
            project_id=PROJECT_ID,
        )

        assert result == "value"
        method_mock.assert_called_once_with(
            name=hook._name(PROJECT_ID, LOCATION, INSTANCE_NAME),
            body=INSTANCE,
            updateMask="instance.name",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook.get_conn"))
    def test_get_instance(self, get_conn_mock, hook):
        method_mock = self.mock_endpoint(get_conn_mock).get
        method_mock.return_value.execute.return_value = "value"
        result = hook.get_instance(instance_name=INSTANCE_NAME, location=LOCATION, project_id=PROJECT_ID)

        assert result == "value"
        method_mock.assert_called_once_with(name=hook._name(PROJECT_ID, LOCATION, INSTANCE_NAME))

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_get_instance_artifacts(self, mock_request, hook):
        scope = "SYSTEM"
        artifact = {
            "name": "test-artifact",
            "version": "1.2.3",
            "scope": scope,
        }
        mock_request.return_value = mock.MagicMock(status=200, data=json.dumps([artifact]))

        hook.get_instance_artifacts(instance_url=INSTANCE_URL, scope=scope)

        mock_request.assert_called_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/artifacts",
            method="GET",
            params={"scope": scope},
        )

    @mock.patch("google.auth.transport.requests.Request")
    @mock.patch(HOOK_STR.format("DataFusionHook.get_credentials"))
    def test_cdap_request(self, get_credentials_mock, mock_request, hook):
        url = "test_url"
        headers = {"Content-Type": "application/json"}
        method = "POST"
        request = mock_request.return_value
        request.return_value = mock.MagicMock()
        body = {"data": "value"}
        params = {"param_key": "param_value"}

        result = hook._cdap_request(url=url, method=method, body=body, params=params)
        mock_request.assert_called_once_with()
        get_credentials_mock.assert_called_once_with()
        get_credentials_mock.return_value.before_request.assert_called_once_with(
            request=request, method=method, url=url, headers=headers
        )
        request.assert_called_once_with(
            method=method, url=url, headers=headers, body=json.dumps(body), params=params
        )
        assert result == request.return_value

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_create_pipeline(self, mock_request, hook):
        mock_request.return_value.status = 200
        hook.create_pipeline(pipeline_name=PIPELINE_NAME, pipeline=PIPELINE, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}",
            method="PUT",
            body=PIPELINE,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_create_pipeline_should_fail_if_empty_data_response(self, mock_request, hook):
        mock_request.return_value.status = 200
        mock_request.return_value.data = None
        with pytest.raises(
            ValueError,
            match=r"Empty response received. Please, check for possible root causes "
            r"of this behavior either in Dag code or on Cloud DataFusion side",
        ):
            hook.create_pipeline(pipeline_name=PIPELINE_NAME, pipeline=PIPELINE, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}",
            method="PUT",
            body=PIPELINE,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_create_pipeline_should_fail_if_status_not_200(self, mock_request, hook):
        mock_request.return_value.status = 404
        with pytest.raises(HTTPError, match=r"Creating a pipeline failed with code 404"):
            hook.create_pipeline(pipeline_name=PIPELINE_NAME, pipeline=PIPELINE, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}",
            method="PUT",
            body=PIPELINE,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_delete_pipeline(self, mock_request, hook):
        mock_request.return_value.status = 200
        hook.delete_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}",
            method="DELETE",
            body=None,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_delete_pipeline_should_fail_if_empty_data_response(self, mock_request, hook):
        mock_request.return_value.status = 200
        mock_request.return_value.data = None
        with pytest.raises(
            ValueError,
            match=r"Empty response received. Please, check for possible root causes "
            r"of this behavior either in Dag code or on Cloud DataFusion side",
        ):
            hook.delete_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}",
            method="DELETE",
            body=None,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_delete_pipeline_should_fail_if_status_not_200(self, mock_request, hook):
        mock_request.return_value.status = 404
        with pytest.raises(HTTPError, match=r"Deleting a pipeline failed with code 404"):
            hook.delete_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}",
            method="DELETE",
            body=None,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_delete_pipeline_should_fail_if_status_409(self, mock_request, hook, caplog):
        caplog.set_level(logging.INFO)
        mock_request.side_effect = [
            MockResponse(status=409, data="Conflict: Resource is still in use."),
            MockResponse(status=200, data="Success"),
        ]
        hook.delete_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL)

        assert mock_request.call_count == 2
        assert "Conflict: Resource is still in use." in caplog.text
        mock_request.assert_called_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}",
            method="DELETE",
            body=None,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_list_pipelines(self, mock_request, hook):
        data = {"data": "test"}
        mock_request.return_value.status = 200
        mock_request.return_value.data = json.dumps(data)
        result = hook.list_pipelines(instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps", method="GET", body=None
        )
        assert result == data

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_list_pipelines_should_fail_if_empty_data_response(self, mock_request, hook):
        mock_request.return_value.status = 200
        mock_request.return_value.data = None
        with pytest.raises(
            ValueError,
            match=r"Empty response received. Please, check for possible root causes "
            r"of this behavior either in Dag code or on Cloud DataFusion side",
        ):
            hook.list_pipelines(instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps", method="GET", body=None
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_list_pipelines_should_fail_if_status_not_200(self, mock_request, hook):
        mock_request.return_value.status = 404
        with pytest.raises(HTTPError, match=r"Listing pipelines failed with code 404"):
            hook.list_pipelines(instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps", method="GET", body=None
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_start_pipeline(self, mock_request, hook):
        run_id = 1234
        mock_request.return_value = mock.MagicMock(status=200, data=f'[{{"runId":{run_id}}}]')

        hook.start_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL, runtime_args=RUNTIME_ARGS)
        body = [
            {
                "appId": PIPELINE_NAME,
                "programType": "workflow",
                "programId": "DataPipelineWorkflow",
                "runtimeargs": RUNTIME_ARGS,
            }
        ]

        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/start",
            method="POST",
            body=body,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_start_pipeline_when_no_response(self, mock_request, hook):
        mock_request.return_value = None
        body = [
            {
                "appId": PIPELINE_NAME,
                "programType": "workflow",
                "programId": "DataPipelineWorkflow",
                "runtimeargs": RUNTIME_ARGS,
            }
        ]
        with pytest.raises(
            ValueError,
            match=r"Failed to start pipeline 'shrubberyPipeline'. Error: Unknown error",
        ):
            hook.start_pipeline(
                pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL, runtime_args=RUNTIME_ARGS
            )
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/start",
            method="POST",
            body=body,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_start_pipeline_stream(self, mock_request, hook):
        run_id = 1234
        mock_request.return_value = mock.MagicMock(status=200, data=f'[{{"runId":{run_id}}}]')

        hook.start_pipeline(
            pipeline_name=PIPELINE_NAME,
            instance_url=INSTANCE_URL,
            runtime_args=RUNTIME_ARGS,
            pipeline_type=DataFusionPipelineType.STREAM,
        )
        body = [
            {
                "appId": PIPELINE_NAME,
                "programType": "spark",
                "programId": "DataStreamsSparkStreaming",
                "runtimeargs": RUNTIME_ARGS,
            }
        ]

        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/start",
            method="POST",
            body=body,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_start_pipeline_should_fail_if_empty_data_response(self, mock_request, hook):
        mock_request.return_value.status = 200
        mock_request.return_value.data = None
        with pytest.raises(
            ValueError,
            match=r"Empty response received. Please, check for possible root causes "
            r"of this behavior either in Dag code or on Cloud DataFusion side",
        ):
            hook.start_pipeline(
                pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL, runtime_args=RUNTIME_ARGS
            )
        body = [
            {
                "appId": PIPELINE_NAME,
                "programType": "workflow",
                "programId": "DataPipelineWorkflow",
                "runtimeargs": RUNTIME_ARGS,
            }
        ]
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/start",
            method="POST",
            body=body,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_start_pipeline_should_fail_if_status_not_200(self, mock_request, hook):
        mock_request.return_value.status = 404
        with pytest.raises(HTTPError, match=r"Starting a pipeline failed with code 404"):
            hook.start_pipeline(
                pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL, runtime_args=RUNTIME_ARGS
            )
        body = [
            {
                "appId": PIPELINE_NAME,
                "programType": "workflow",
                "programId": "DataPipelineWorkflow",
                "runtimeargs": RUNTIME_ARGS,
            }
        ]
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/start",
            method="POST",
            body=body,
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_stop_pipeline(self, mock_request, hook):
        mock_request.return_value.status = 200
        hook.stop_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"workflows/DataPipelineWorkflow/stop",
            method="POST",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_stop_pipeline_with_run_id(self, mock_request, hook):
        mock_request.return_value.status = 200
        hook.stop_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL, run_id="eaf-2fr-4rf")
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"workflows/DataPipelineWorkflow/runs/eaf-2fr-4rf/stop",
            method="POST",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_stop_pipeline_should_fail_if_empty_data_response(self, mock_request, hook):
        mock_request.return_value.status = 200
        mock_request.return_value.data = None
        with pytest.raises(
            ValueError,
            match=r"Empty response received. Please, check for possible root causes "
            r"of this behavior either in Dag code or on Cloud DataFusion side",
        ):
            hook.stop_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"workflows/DataPipelineWorkflow/stop",
            method="POST",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_stop_pipeline_should_fail_if_empty_data_response_with_run_id(self, mock_request, hook):
        mock_request.return_value.status = 200
        mock_request.return_value.data = None
        with pytest.raises(
            ValueError,
            match=r"Empty response received. Please, check for possible root causes "
            r"of this behavior either in Dag code or on Cloud DataFusion side",
        ):
            hook.stop_pipeline(
                pipeline_name=PIPELINE_NAME,
                instance_url=INSTANCE_URL,
                pipeline_type=DataFusionPipelineType.STREAM,
                run_id="eaf-2fr-4rf",
            )
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"spark/DataStreamsSparkStreaming/runs/eaf-2fr-4rf/stop",
            method="POST",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_stop_pipeline_should_fail_if_status_not_200(self, mock_request, hook):
        mock_request.return_value.status = 404
        with pytest.raises(HTTPError, match=r"Stopping a pipeline failed with code 404"):
            hook.stop_pipeline(pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL)
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"workflows/DataPipelineWorkflow/stop",
            method="POST",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_get_pipeline_workflow(self, mock_request, hook):
        run_id = 1234
        mock_request.return_value = mock.MagicMock(status=200, data=f'[{{"runId":{run_id}}}]')
        hook.get_pipeline_workflow(
            pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL, pipeline_id=PIPELINE_ID
        )
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"workflows/DataPipelineWorkflow/runs/{PIPELINE_ID}",
            method="GET",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_get_pipeline_workflow_stream(self, mock_request, hook):
        run_id = 1234
        mock_request.return_value = mock.MagicMock(status=200, data=f'[{{"runId":{run_id}}}]')
        hook.get_pipeline_workflow(
            pipeline_name=PIPELINE_NAME,
            instance_url=INSTANCE_URL,
            pipeline_id=PIPELINE_ID,
            pipeline_type=DataFusionPipelineType.STREAM,
        )
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"spark/DataStreamsSparkStreaming/runs/{PIPELINE_ID}",
            method="GET",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_get_pipeline_workflow_should_fail_if_empty_data_response(self, mock_request, hook):
        mock_request.return_value.status = 200
        mock_request.return_value.data = None
        with pytest.raises(
            ValueError,
            match=r"Empty response received. Please, check for possible root causes "
            r"of this behavior either in Dag code or on Cloud DataFusion side",
        ):
            hook.get_pipeline_workflow(
                pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL, pipeline_id=PIPELINE_ID
            )
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"workflows/DataPipelineWorkflow/runs/{PIPELINE_ID}",
            method="GET",
        )

    @mock.patch(HOOK_STR.format("DataFusionHook._cdap_request"))
    def test_get_pipeline_workflow_should_fail_if_status_not_200(self, mock_request, hook):
        mock_request.return_value.status = 404
        with pytest.raises(HTTPError, match=r"Retrieving a pipeline state failed with code 404"):
            hook.get_pipeline_workflow(
                pipeline_name=PIPELINE_NAME, instance_url=INSTANCE_URL, pipeline_id=PIPELINE_ID
            )
        mock_request.assert_called_once_with(
            url=f"{INSTANCE_URL}/v3/namespaces/default/apps/{PIPELINE_NAME}/"
            f"workflows/DataPipelineWorkflow/runs/{PIPELINE_ID}",
            method="GET",
        )

    @pytest.mark.parametrize(
        ("pipeline_type", "expected_program_type"),
        [
            (DataFusionPipelineType.BATCH, "workflows"),
            (DataFusionPipelineType.STREAM, "spark"),
            ("non existing value", ""),
        ],
    )
    def test_cdap_program_type(self, pipeline_type, expected_program_type):
        assert DataFusionHook.cdap_program_type(pipeline_type) == expected_program_type

    @pytest.mark.parametrize(
        ("pipeline_type", "expected_program_id"),
        [
            (DataFusionPipelineType.BATCH, "DataPipelineWorkflow"),
            (DataFusionPipelineType.STREAM, "DataStreamsSparkStreaming"),
            ("non existing value", ""),
        ],
    )
    def test_cdap_program_id(self, pipeline_type, expected_program_id):
        assert DataFusionHook.cdap_program_id(pipeline_type) == expected_program_id


class TestDataFusionHookAsynch:
    @pytest.mark.asyncio
    @mock.patch(HOOK_STR.format("DataFusionAsyncHook._get_link"))
    async def test_async_get_pipeline_should_execute_successfully(self, mocked_link, hook_async):
        await hook_async.get_pipeline(
            instance_url=INSTANCE_URL,
            namespace=NAMESPACE,
            pipeline_name=PIPELINE_NAME,
            pipeline_id=PIPELINE_ID,
            session=session,
        )
        mocked_link.assert_awaited_once_with(url=CONSTRUCTED_PIPELINE_URL, session=session)

    @pytest.mark.asyncio
    @pytest.mark.parametrize(
        ("pipeline_type", "constructed_url"),
        [
            (DataFusionPipelineType.BATCH, CONSTRUCTED_PIPELINE_URL_GET),
            (DataFusionPipelineType.STREAM, CONSTRUCTED_PIPELINE_STREAM_URL_GET),
        ],
    )
    @mock.patch(HOOK_STR.format("DataFusionAsyncHook.get_pipeline"))
    async def test_async_get_pipeline_status_completed_should_execute_successfully(
        self, mocked_get, hook_async, pipeline_type, constructed_url
    ):
        mocked_get.return_value = MockAiohttpClientResponse(
            status=200,
            payload={"status": "COMPLETED"},
            method="GET",
            url=constructed_url,
        )

        pipeline_status = await hook_async.get_pipeline_status(
            pipeline_name=PIPELINE_NAME,
            instance_url=INSTANCE_URL,
            pipeline_id=PIPELINE_ID,
            namespace=NAMESPACE,
        )
        mocked_get.assert_awaited_once()
        assert pipeline_status == "success"

    @pytest.mark.asyncio
    @pytest.mark.parametrize(
        ("pipeline_type", "constructed_url"),
        [
            (DataFusionPipelineType.BATCH, CONSTRUCTED_PIPELINE_URL_GET),
            (DataFusionPipelineType.STREAM, CONSTRUCTED_PIPELINE_STREAM_URL_GET),
        ],
    )
    @mock.patch(HOOK_STR.format("DataFusionAsyncHook.get_pipeline"))
    async def test_async_get_pipeline_status_running_should_execute_successfully(
        self, mocked_get, hook_async, pipeline_type, constructed_url
    ):
        """Assets that the DataFusionAsyncHook returns pending response when job is still in running state"""
        mocked_get.return_value = MockAiohttpClientResponse(
            status=200,
            payload={"status": "RUNNING"},
            method="GET",
            url=constructed_url,
        )

        pipeline_status = await hook_async.get_pipeline_status(
            pipeline_name=PIPELINE_NAME,
            instance_url=INSTANCE_URL,
            pipeline_id=PIPELINE_ID,
            pipeline_type=pipeline_type,
            namespace=NAMESPACE,
        )
        mocked_get.assert_awaited_once()
        assert pipeline_status == "pending"

    @pytest.mark.asyncio
    @mock.patch(HOOK_STR.format("DataFusionAsyncHook.get_pipeline"))
    async def test_async_get_pipeline_status_os_error_should_execute_successfully(
        self, mocked_get, hook_async
    ):
        """Assets that the DataFusionAsyncHook returns a pending response when OSError is raised"""
        mocked_get.side_effect = OSError()

        pipeline_status = await hook_async.get_pipeline_status(
            pipeline_name=PIPELINE_NAME,
            instance_url=INSTANCE_URL,
            pipeline_id=PIPELINE_ID,
            namespace=NAMESPACE,
        )
        mocked_get.assert_awaited_once()
        assert pipeline_status == "pending"

    @pytest.mark.asyncio
    @mock.patch(HOOK_STR.format("DataFusionAsyncHook.get_pipeline"))
    async def test_async_get_pipeline_status_exception_should_execute_successfully(
        self, mocked_get, hook_async, caplog
    ):
        """Assets that the logging is done correctly when DataFusionAsyncHook raises Exception"""
        caplog.set_level(logging.INFO)
        mocked_get.side_effect = ValueError()

        await hook_async.get_pipeline_status(
            pipeline_name=PIPELINE_NAME,
            instance_url=INSTANCE_URL,
            pipeline_id=PIPELINE_ID,
            namespace=NAMESPACE,
        )
        mocked_get.assert_awaited_once()
        assert "Retrieving pipeline status finished with errors..." in caplog.text
