import json
import os
import pathlib
from unittest import TestCase
from unittest.mock import Mock, patch, call, ANY
from click.testing import CliRunner

import click
from box import Box
from freezegun import freeze_time
from pycarlo.core import Client

from montecarlodata.common.data import MonolithResponse
from montecarlodata.iac.commands import compile as compile_config
from montecarlodata.iac.mc_config_service import MonteCarloConfigService
from montecarlodata.queries.iac import CREATE_OR_UPDATE_MONTE_CARLO_CONFIG_TEMPLATE
from montecarlodata.utils import GqlWrapper
from tests.test_common_user import _SAMPLE_CONFIG


class MonteCarloConfigServiceTest(TestCase):
    maxDiff = None

    @patch("os.getcwd")
    def setUp(self, getcwd) -> None:
        self._request_wrapper_mock = Mock(spec=GqlWrapper)
        self._pycarlo_client = Mock(spec=Client)
        self._print_func = Mock(spec=click.echo)
        self.project_dir = self._get_project_dir("standalone_configs")
        getcwd.return_value = self.project_dir
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            pycarlo_client=self._pycarlo_client,
            print_func=self._print_func,
        )

    def test_standalone_configs(self):
        files, template, _ = self.service.compile("test")
        self.assertEqual(len(files), 3)
        self.assertEqual(
            template,
            {
                "test": {
                    "field_health": [
                        {"table": "analytics:prod.client_hub", "fields": ["account_id"]}
                    ],
                    "dimension_tracking": [
                        {"table": "analytics:prod.customer_360", "field": "account_id"}
                    ],
                },
            },
        )

    def test_namespace_override(self):
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            project_dir=self._get_project_dir("namespace_override"),
            pycarlo_client=self._pycarlo_client,
        )
        files, template, _ = self.service.compile("test")

        self.assertEqual(len(files), 3)
        self.assertEqual(
            template,
            {
                "custom_namespace": {
                    "field_health": [
                        {
                            "table": "analytics:prod_lineage.lineage_nodes",
                            "timestamp_field": "created",
                        },
                        {"table": "analytics:prod.abc"},
                    ],
                    "freshness": [
                        {
                            "table": "analytics:prod.abc",
                            "freshness_threshold": 30,
                            "schedule": {
                                "type": "fixed",
                                "interval_minutes": 30,
                                "start_time": "2021-07-27T19:51:00",
                            },
                        }
                    ],
                },
                "infra": {
                    "dimension_tracking": [
                        {"table": "analytics:prod.customer_360", "field": "account_id"}
                    ],
                },
                "test": {
                    "dimension_tracking": [
                        {"table": "analytics:prod.customer_360", "field": "account_id"}
                    ],
                },
            },
        )

    def test_namespace_override_no_default_namesapce(self):
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            project_dir=self._get_project_dir("namespace_override"),
            pycarlo_client=self._pycarlo_client,
        )
        files, template, errors = self.service.compile(None, abort_on_error=False)

        self.assertEqual(len(files), 3)
        self.assertEqual(
            template,
            {
                "custom_namespace": {
                    "field_health": [
                        {
                            "table": "analytics:prod_lineage.lineage_nodes",
                            "timestamp_field": "created",
                        },
                        {"table": "analytics:prod.abc"},
                    ],
                    "freshness": [
                        {
                            "table": "analytics:prod.abc",
                            "freshness_threshold": 30,
                            "schedule": {
                                "type": "fixed",
                                "interval_minutes": 30,
                                "start_time": "2021-07-27T19:51:00",
                            },
                        }
                    ],
                },
                "infra": {
                    "dimension_tracking": [
                        {"table": "analytics:prod.customer_360", "field": "account_id"}
                    ],
                },
            },
        )
        self.assertEqual(len(errors), 1)
        file_name = next(iter(errors))
        self.assertTrue(
            "tests/iac/test_resources/namespace_override/monitors_other.yml"
            in file_name
        )
        self.assertTrue(
            "A default namespace need to be passed through command line --namespace "
            "or set default namespace in montecarlo.yml or an override namespace needs to be set in file"
            in errors[file_name][0]
        )

    def test_namespace_override_default_namespace_montecarlo_yaml(self):
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            project_dir=self._get_project_dir("namespace_override_montecarlo"),
            pycarlo_client=self._pycarlo_client,
        )

        files, template, errors = self.service.compile(None, abort_on_error=False)

        self.assertEqual(len(files), 3)
        self.assertEqual(
            template,
            {
                "custom_namespace": {
                    "field_health": [
                        {
                            "table": "analytics:prod_lineage.lineage_nodes",
                            "timestamp_field": "created",
                        },
                        {"table": "analytics:prod.abc"},
                    ],
                    "freshness": [
                        {
                            "table": "analytics:prod.abc",
                            "freshness_threshold": 30,
                            "schedule": {
                                "type": "fixed",
                                "interval_minutes": 30,
                                "start_time": "2021-07-27T19:51:00",
                            },
                        }
                    ],
                },
                "infra": {
                    "dimension_tracking": [
                        {"table": "analytics:prod.customer_360", "field": "account_id"}
                    ],
                },
                "test_montecarlo": {
                    "dimension_tracking": [
                        {"table": "analytics:prod.customer_360", "field": "account_id"}
                    ],
                },
            },
        )
        self.assertEqual(len(errors), 0)

    def test_embedded_dbt_configs(self):
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            project_dir=self._get_project_dir("embedded_dbt_configs"),
            pycarlo_client=self._pycarlo_client,
        )
        files, template, _ = self.service.compile("test")

        self.assertEqual(len(files), 4)
        self.assertEqual(
            template,
            {
                "test": {
                    "field_health": [
                        {
                            "table": "analytics:prod_lineage.lineage_nodes",
                            "timestamp_field": "created",
                        },
                        {"table": "analytics:prod.abc"},
                        {
                            "table": "analytics:prod.client_hub",
                            "fields": ["account_id"],
                        },
                    ],
                    "freshness": [
                        {
                            "table": "analytics:prod.abc",
                            "freshness_threshold": 30,
                            "schedule": {
                                "type": "fixed",
                                "interval_minutes": 30,
                                "start_time": "2021-07-27T19:51:00",
                            },
                        }
                    ],
                    "dimension_tracking": [
                        {"table": "analytics:prod.customer_360", "field": "account_id"}
                    ],
                },
            },
        )

    def test_embedded_dbt_configs_with_refs(self):
        project_dir = self._get_project_dir("embedded_dbt_configs_with_refs")
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            project_dir=project_dir,
            pycarlo_client=self._pycarlo_client,
            dbt_manifest_path=f"{project_dir}/manifest.json",
        )
        files, template, _ = self.service.compile("test")

        self.assertEqual(len(files), 4)
        self.assertEqual(
            template,
            {
                "test": {
                    "field_health": [
                        {
                            "table": "analytics:prod_lineage.lineage_nodes",
                            "timestamp_field": "created",
                        },
                        {"table": "analytics:prod.def"},
                        {
                            "table": "analytics:prod.client_hub",
                            "name": "a analytics:prod.client_hub or analytics:prod.def",
                            "fields": ["account_id"],
                        },
                    ],
                    "freshness": [
                        {
                            "tables": [
                                "analytics:prod.def",
                                "analytics:prod.client_hub",
                            ],
                            "freshness_threshold": 30,
                            "schedule": {
                                "type": "fixed",
                                "interval_minutes": 30,
                                "start_time": "2021-07-27T19:51:00",
                            },
                        }
                    ],
                    "dimension_tracking": [
                        {"table": "analytics:prod.customer_360", "field": "account_id"}
                    ],
                    "custom_sql": [
                        {
                            "sql": "select * from analytics.prod.def a join"
                            " analytics.prod.client_hub b on a.id = b.id",
                            "sampling_sql": "select * from analytics.prod.customer_360",
                            "variables": {"foo": ["bar analytics.prod.def"]},
                            "notes": "{{query_result:link, connection_type}}",
                        },
                    ],
                },
            },
        )

    def test_invalid_configs(self):
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            project_dir=self._get_project_dir("invalid_configs"),
            pycarlo_client=self._pycarlo_client,
        )
        self.service._abort_on_error = False

        files, template, errors_by_file = self.service.compile(
            "test", abort_on_error=False
        )
        errors = sorted(list(errors_by_file.items()), key=lambda x: x[0])

        file, error = errors[0]
        self.assertTrue(file.endswith("dir1/dir2/monitors.yml"))
        self.assertEqual(error, ['"custom_sql" property should be a list.'])
        file, error = errors[1]
        self.assertTrue(file.endswith("dir1/monitors.yml"))
        self.assertEqual(error, ['"field_health" property should be a list.'])

    def test_apply(self):
        namespace = "foo"
        self._request_wrapper_mock.make_request_v2.return_value = MonolithResponse(
            data={
                "response": {
                    "resourceModifications": [
                        {
                            "type": "ResourceModificationType.UPDATE",
                            "description": "Monitor: type=stats, table=analytics:prod.client_hub",
                            "resourceAsJson": json.dumps(
                                {
                                    "uuid": "ed4d07c3-58fd-44d0-8b2d-c1b020f45a69",
                                    "resource": None,
                                    "name": (
                                        "monitor|type=stats|table=analytics:prod.client_hub|"
                                        "timestamp_field=<<NULL>>|where_condition=<<NULL>>"
                                    ),
                                    "table": "analytics:prod.customer_360",
                                    "type": "stats",
                                    "fields": [],
                                    "timestamp_field": None,
                                    "where_condition": None,
                                }
                            ),
                        },
                        {
                            "type": "ResourceModificationType.UPDATE",
                            "description": "Monitor: type=categories, table=analytics:prod.customer_360",
                            "resourceAsJson": json.dumps(
                                {
                                    "uuid": "ec3b0a80-d088-4dbe-acf5-150caf041574",
                                    "resource": None,
                                    "name": (
                                        "monitor|type=categories|table=analytics:prod.customer_360|"
                                        "timestamp_field=<<NULL>>|where_condition=<<NULL>>|fields=account_id"
                                    ),
                                    "table": "analytics:prod.customer_360",
                                    "type": "categories",
                                    "fields": ["account_id"],
                                    "timestamp_field": None,
                                    "where_condition": None,
                                }
                            ),
                        },
                    ],
                    "changesApplied": True,
                    "errorsAsJson": "{}",
                    "warningsAsJson": "{}",
                }
            }
        )

        responses = self.service.apply(namespace, skip_confirmation=True)

        config_template_as_dict = {
            "field_health": [
                {"table": "analytics:prod.client_hub", "fields": ["account_id"]}
            ],
            "dimension_tracking": [
                {"table": "analytics:prod.customer_360", "field": "account_id"}
            ],
        }

        self._request_wrapper_mock.make_request_v2.assert_has_calls(
            [
                call(
                    query=CREATE_OR_UPDATE_MONTE_CARLO_CONFIG_TEMPLATE,
                    operation="createOrUpdateMonteCarloConfigTemplate",
                    variables=dict(
                        namespace=namespace,
                        configTemplateJson=json.dumps(config_template_as_dict),
                        dryRun=True,
                        misconfiguredAsWarning=True,
                        resource=None,
                    ),
                ),
                call(
                    query=CREATE_OR_UPDATE_MONTE_CARLO_CONFIG_TEMPLATE,
                    operation="createOrUpdateMonteCarloConfigTemplate",
                    variables=dict(
                        namespace=namespace,
                        configTemplateJson=json.dumps(config_template_as_dict),
                        dryRun=False,
                        misconfiguredAsWarning=True,
                        resource=None,
                    ),
                ),
            ]
        )

        self.assertEqual(responses[0].errors, {})
        self.assertEqual(len(responses[0].resource_modifications), 2)

    def test_apply_dry_run(self):
        namespace = "foo"
        self._request_wrapper_mock.make_request_v2.return_value = MonolithResponse(
            data={
                "response": {
                    "resourceModifications": [
                        {
                            "type": "ResourceModificationType.UPDATE",
                            "description": "Monitor: type=stats, table=analytics:prod.client_hub",
                            "resourceAsJson": '{"uuid": "ed4d07c3-58fd-44d0-8b2d-c1b020f45a69", "resource": null, "name": "monitor|type=stats|table=analytics:prod.client_hub|timestamp_field=<<NULL>>|where_condition=<<NULL>>", "table": "analytics:prod.customer_360", "type": "stats", "fields": [], "timestamp_field": null, "where_condition": null}',
                        },
                        {
                            "type": "ResourceModificationType.UPDATE",
                            "description": "Monitor: type=categories, table=analytics:prod.customer_360",
                            "resourceAsJson": '{"uuid": "ec3b0a80-d088-4dbe-acf5-150caf041574", "resource": null, "name": "monitor|type=categories|table=analytics:prod.customer_360|timestamp_field=<<NULL>>|where_condition=<<NULL>>|fields=account_id", "table": "analytics:prod.customer_360", "type": "categories", "fields": ["account_id"], "timestamp_field": null, "where_condition": null}',
                        },
                    ],
                    "changesApplied": True,
                    "errorsAsJson": "{}",
                    "warningsAsJson": "{}",
                }
            }
        )

        responses = self.service.apply(namespace, dry_run=True)

        config_template_as_dict = {
            "field_health": [
                {"table": "analytics:prod.client_hub", "fields": ["account_id"]}
            ],
            "dimension_tracking": [
                {"table": "analytics:prod.customer_360", "field": "account_id"}
            ],
        }

        self._request_wrapper_mock.make_request_v2.assert_called_once_with(
            query=CREATE_OR_UPDATE_MONTE_CARLO_CONFIG_TEMPLATE,
            operation="createOrUpdateMonteCarloConfigTemplate",
            variables=dict(
                namespace=namespace,
                configTemplateJson=json.dumps(config_template_as_dict),
                dryRun=True,
                misconfiguredAsWarning=True,
                resource=None,
            ),
        )

        self.assertEqual(responses[0].errors, {})
        self.assertEqual(len(responses[0].resource_modifications), 2)

    def test_apply_with_errors(self):
        namespace = "foo"
        self._request_wrapper_mock.make_request_v2.return_value = MonolithResponse(
            data={
                "response": {
                    "resourceModifications": [],
                    "changesApplied": False,
                    "errorsAsJson": '{"validation_errors": {"monitors": {"0": {"type": ["Unknown field."]}}}}',
                }
            }
        )

        responses = self.service.apply(namespace, abort_on_error=False)

        config_template_as_dict = {
            "field_health": [
                {"table": "analytics:prod.client_hub", "fields": ["account_id"]}
            ],
            "dimension_tracking": [
                {"table": "analytics:prod.customer_360", "field": "account_id"}
            ],
        }

        self._request_wrapper_mock.make_request_v2.assert_called_once_with(
            query=CREATE_OR_UPDATE_MONTE_CARLO_CONFIG_TEMPLATE,
            operation="createOrUpdateMonteCarloConfigTemplate",
            variables=dict(
                namespace=namespace,
                configTemplateJson=json.dumps(config_template_as_dict),
                dryRun=True,
                misconfiguredAsWarning=True,
                resource=None,
            ),
        )

        self.assertEqual(
            responses[0].errors,
            {"validation_errors": {"monitors": {"0": {"type": ["Unknown field."]}}}},
        )
        self.assertEqual(len(responses[0].resource_modifications), 0)

    def test_misconfigured_warnings(self):
        namespace = "foo"
        self._request_wrapper_mock.make_request_v2.return_value = MonolithResponse(
            data={
                "response": {
                    "resourceModifications": [
                        {
                            "type": "ResourceModificationType.CREATE",
                            "description": (
                                "Freshness SLO: "
                                "tables=['analytics:prod.client_hub', 'analytics:prod.client_warehouses'] "
                                "freshness_threshold=1"
                            ),
                            "resourceAsJson": json.dumps(
                                {
                                    "uuid": None,
                                    "resource": None,
                                    "name": "freshnessrule|tables=(multiple)",
                                    "description": None,
                                    "notes": None,
                                    "labels": [],
                                    "schedule": {
                                        "type": "fixed",
                                        "interval_minutes": 1,
                                        "interval_crontab": None,
                                        "start_time": "2021-07-27T19:00:00",
                                    },
                                    "table": None,
                                    "tables": [
                                        "analytics:prod.client_hub",
                                        "analytics:prod.client_warehouses",
                                    ],
                                    "freshness_threshold": 1,
                                }
                            ),
                        }
                    ],
                    "changesApplied": False,
                    "errorsAsJson": "{}",
                    "warningsAsJson": json.dumps(
                        {
                            "misconfigured_warnings": [
                                {
                                    "title": "High breaching Freshness SLOs are defined, adjust freshness_threshold in",
                                    "items": [
                                        (
                                            "analytics:prod.client_hub: "
                                            "freshness_threshold is 1, the expected minimum threshold is 464"
                                        ),
                                        (
                                            "analytics:prod.client_warehouses: "
                                            "freshness_threshold is 1, the expected minimum threshold is 1440"
                                        ),
                                    ],
                                }
                            ]
                        }
                    ),
                }
            }
        )

        responses = self.service.apply(
            namespace, abort_on_error=False, skip_confirmation=True
        )

        self._request_wrapper_mock.make_request_v2.assert_has_calls(
            [
                call(
                    query=CREATE_OR_UPDATE_MONTE_CARLO_CONFIG_TEMPLATE,
                    operation="createOrUpdateMonteCarloConfigTemplate",
                    variables=dict(
                        namespace=namespace,
                        configTemplateJson=ANY,
                        dryRun=True,
                        misconfiguredAsWarning=True,
                        resource=None,
                    ),
                ),
                call(
                    query=CREATE_OR_UPDATE_MONTE_CARLO_CONFIG_TEMPLATE,
                    operation="createOrUpdateMonteCarloConfigTemplate",
                    variables=dict(
                        namespace=namespace,
                        configTemplateJson=ANY,
                        dryRun=False,
                        misconfiguredAsWarning=True,
                        resource=None,
                    ),
                ),
            ]
        )

        self.assertEqual(
            responses[0].warnings,
            {
                "misconfigured_warnings": [
                    {
                        "items": [
                            (
                                "analytics:prod.client_hub: freshness_threshold is 1, the expected "
                                "minimum threshold is 464"
                            ),
                            (
                                "analytics:prod.client_warehouses: freshness_threshold is 1, the expected "
                                "minimum threshold is 1440"
                            ),
                        ],
                        "title": "High breaching Freshness SLOs are defined, adjust freshness_threshold in",
                    }
                ]
            },
        )
        self.assertEqual(len(responses[0].resource_modifications), 1)

    def _get_project_dir(self, dir_name: str):
        return os.path.join(
            pathlib.Path(__file__).parent.resolve(), "test_resources", dir_name
        )

    LIMIT = 2
    NAMESPACES_TABLE = """\
╒═════════════╤══════════════════════════════════╕
│ Namespace   │ Last Update Time                 │
╞═════════════╪══════════════════════════════════╡
│ namespace_1 │ 2000-01-01 00:00:00.000000+00:00 │
├─────────────┼──────────────────────────────────┤
│ namespace_2 │ 2000-01-01 00:00:00.000000+00:00 │
╘═════════════╧══════════════════════════════════╛"""

    @staticmethod
    def _mc_config_templates_response(namespace_count):
        return Box(
            {
                "get_monte_carlo_config_templates": {
                    "edges": [
                        {
                            "node": {
                                "namespace": f"namespace_{i}",
                                "last_update_time": "2000-01-01 00:00:00.000000+00:00",
                            }
                        }
                        for i in range(1, namespace_count + 1)
                    ]
                }
            }
        )

    def test_list_namespaces(self):
        self._pycarlo_client.return_value = self._mc_config_templates_response(
            self.LIMIT
        )
        self.service.list_namespaces(self.LIMIT)
        self._print_func.assert_called_once_with(self.NAMESPACES_TABLE)

    def test_list_namespaces_with_more_available(self):
        self._pycarlo_client.return_value = self._mc_config_templates_response(
            self.LIMIT + 1
        )
        self.service.list_namespaces(self.LIMIT)
        expected_calls = [
            call(self.NAMESPACES_TABLE),
            call(self.service.MORE_NS_MESSAGE),
        ]
        self._print_func.assert_has_calls(expected_calls)

    def test_compile_command_no_namespace(self):
        ctx = _SAMPLE_CONFIG
        runner = CliRunner()
        dir = self._get_project_dir("namespace_override")
        result = runner.invoke(
            compile_config,
            obj={"config": ctx},
            args=["--project-dir", dir, "--namespace", None],
        )

        self.assertEqual(
            result.output,
            (
                "\n"
                "Gathering monitor configuration files.\n"
                " - "
                f"{dir}/dbt_models/schema.yml "
                "- Embedded monitor configuration found.\n"
                " - "
                f"{dir}/monitors.yml "
                "- Monitor configuration found.\n"
                "\n"
                "Configuration validation errors:\n"
                " - File: "
                f"{dir}/monitors_other.yml\n"
                "    - A default namespace need to be passed through command line --namespace "
                "or set default namespace in montecarlo.yml or an override namespace needs to "
                "be set in file: "
                f"{dir}/monitors_other.yml\n"
                "\n"
                "Error - Errors encountered, exiting.\n"
                "Aborted!\n"
            ),
        )

    def test_compile_command_ns_config(self):
        ctx = _SAMPLE_CONFIG
        runner = CliRunner()
        dir = self._get_project_dir("namespace_override_montecarlo")
        result = runner.invoke(
            compile_config,
            obj={"config": ctx},
            args=["--project-dir", dir, "--namespace", None],
        )

        self.assertEqual(
            result.output,
            (
                "\n"
                "Gathering monitor configuration files.\n"
                "namespace: test_montecarlo found in montecarlo.yml ignoring value passed "
                "with --namespace=None\n"
                " - "
                f"{dir}/dbt_models/schema.yml "
                "- Embedded monitor configuration found.\n"
                " - "
                f"{dir}/monitors.yml "
                "- Monitor configuration found.\n"
                " - "
                f"{dir}/monitors_other.yml "
                "- Monitor configuration found.\n"
            ),
        )

    def test_compile_command_ns_param(self):
        ctx = _SAMPLE_CONFIG
        runner = CliRunner()
        dir = self._get_project_dir("namespace_override")
        result = runner.invoke(
            compile_config,
            obj={"config": ctx},
            args=["--project-dir", dir, "--namespace", "test-mont"],
        )

        self.assertEqual(
            result.output,
            (
                "\n"
                "Gathering monitor configuration files.\n"
                " - "
                f"{dir}/dbt_models/schema.yml "
                "- Embedded monitor configuration found.\n"
                " - "
                f"{dir}/monitors.yml "
                "- Monitor configuration found.\n"
                " - "
                f"{dir}/monitors_other.yml "
                "- Monitor configuration found.\n"
            ),
        )

    def test_compile_command_ns_config_param(self):
        ctx = _SAMPLE_CONFIG
        runner = CliRunner()
        dir = self._get_project_dir("namespace_override_montecarlo")
        result = runner.invoke(
            compile_config,
            obj={"config": ctx},
            args=["--project-dir", dir, "--namespace", "test-namespace"],
        )

        self.assertEqual(
            result.output,
            (
                "\n"
                "Gathering monitor configuration files.\n"
                "namespace: test_montecarlo found in montecarlo.yml ignoring value passed "
                "with --namespace=test-namespace\n"
                " - "
                f"{dir}/dbt_models/schema.yml "
                "- Embedded monitor configuration found.\n"
                " - "
                f"{dir}/monitors.yml "
                "- Monitor configuration found.\n"
                " - "
                f"{dir}/monitors_other.yml "
                "- Monitor configuration found.\n"
            ),
        )

    @freeze_time("2020-01-01")
    def test_generate_from_dbt_tests(self):
        project_dir = self._get_project_dir("import_dbt_tests")
        output_path = f"{project_dir}/dbt.yml"
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            pycarlo_client=self._pycarlo_client,
            dbt_manifest_path=f"{project_dir}/manifest.json",
        )
        self.service.generate_from_dbt_tests(output_path)

        with open(f"{project_dir}/expected.yml", "r") as f:
            expected_yaml = f.read()

        with open(output_path, "r") as f:
            output_yaml = f.read()

        self.assertEqual(expected_yaml, output_yaml)

    @freeze_time("2020-01-01")
    def test_generate_from_dbt_tests_when_filtered_and_labeled(self):
        project_dir = self._get_project_dir("import_dbt_tests")
        output_path = f"{project_dir}/dbt.yml"
        self.service = MonteCarloConfigService(
            _SAMPLE_CONFIG,
            request_wrapper=self._request_wrapper_mock,
            pycarlo_client=self._pycarlo_client,
            dbt_manifest_path=f"{project_dir}/manifest.json",
        )
        self.service.generate_from_dbt_tests(
            output_path,
            test_types=["SINGULAR", "unique"],
            labels=["foo", "bar"],
        )

        with open(output_path, "r") as f:
            output_yaml = f.read()

        self.assertEqual(
            """\
montecarlo:
  custom_sql:
  - name: test.analytics.assert_not_null_recent_metrics_resource_id
    description: test.analytics.assert_not_null_recent_metrics_resource_id
    sql: |
      SELECT
          *
      FROM
          personal.dbt_bneville.recent_metrics
      WHERE
          resource_id IS NULL AND
          metric != 'total_views'
    schedule:
      type: fixed
      start_time: '2020-01-01T00:00:00+00:00'
      interval_minutes: 720
    comparisons:
    - type: threshold
      operator: GT
      threshold_value: 0
    labels:
    - bar
    - foo
  - name: test.analytics.unique_table_field_metrics_multi_key.14e928c2e4
    description: test.analytics.unique_table_field_metrics_multi_key.14e928c2e4
    sql: |
      select
          multi_key as unique_field,
          count(*) as n_records

      from personal.dbt_bneville.table_field_metrics
      where multi_key is not null
      group by multi_key
      having count(*) > 1
    schedule:
      type: fixed
      start_time: '2020-01-01T00:00:00+00:00'
      interval_minutes: 720
    comparisons:
    - type: threshold
      operator: GT
      threshold_value: 0
    labels:
    - bar
    - foo\n""",
            output_yaml,
        )
