Repository: dbt-labs/dbt-core

Issue:
[CT-1317] Validate source config enabled flag
There is no validation of the "enabled" config flag for sources, whether in dbt_project.yml or in a schema file. This means that people can set the flag to strange things such as "False and False" and it will be saved with no error message.


Discussion:
There doesn't seem to be validation for the enabled config for metrics and exposures either.  Exposures seem to behave like sources.  Metrics will fail, but with a misleading error message.

This `schema.yml`

```yaml
version: 2

metrics:

  - name: number_of_people
    label: "Number of people"
    description: Total count of people
    model: "ref('people')"
    calculation_method: count
    expression: "*"
    timestamp: created_at
    time_grains: [day, week, month]
    dimensions:
      - favorite_color
      - loves_dbt
    meta:
        my_meta: 'testing'

  - name: collective_tenure
    label: "Collective tenure"
    description: Total number of years of team experience
    model: "metric('number_of_people')"
    calculation_method: sum
    expression: tenure
    config:
      enabled: False and False
    timestamp: created_at
    time_grains: [day]
    filters:
      - field: loves_dbt
        operator: 'is'
        value: 'true'
```

results in 

```bash
Compilation Error in metric collective_tenure (models/metric_y.yml)
  Metric 'metric.basic_dbt.collective_tenure' (models/metric_y.yml) depends on a metric named 'number_of_people' which is disabled
```

When in fact `collective_tenure` has an invalid config and `number_of_people` is enabled.

Resolving patch:
diff --git a/core/dbt/contracts/graph/model_config.py b/core/dbt/contracts/graph/model_config.py
--- a/core/dbt/contracts/graph/model_config.py
+++ b/core/dbt/contracts/graph/model_config.py
@@ -376,40 +376,6 @@ class ExposureConfig(BaseConfig):
 @dataclass
 class SourceConfig(BaseConfig):
     enabled: bool = True
-    # to be implmented to complete CT-201
-    # quoting: Dict[str, Any] = field(
-    #     default_factory=dict,
-    #     metadata=MergeBehavior.Update.meta(),
-    # )
-    # freshness: Optional[Dict[str, Any]] = field(
-    #     default=None,
-    #     metadata=CompareBehavior.Exclude.meta(),
-    # )
-    # loader: Optional[str] = field(
-    #     default=None,
-    #     metadata=CompareBehavior.Exclude.meta(),
-    # )
-    # # TODO what type is this? docs say: "<column_name_or_expression>"
-    # loaded_at_field: Optional[str] = field(
-    #     default=None,
-    #     metadata=CompareBehavior.Exclude.meta(),
-    # )
-    # database: Optional[str] = field(
-    #     default=None,
-    #     metadata=CompareBehavior.Exclude.meta(),
-    # )
-    # schema: Optional[str] = field(
-    #     default=None,
-    #     metadata=CompareBehavior.Exclude.meta(),
-    # )
-    # meta: Dict[str, Any] = field(
-    #     default_factory=dict,
-    #     metadata=MergeBehavior.Update.meta(),
-    # )
-    # tags: Union[List[str], str] = field(
-    #     default_factory=list_str,
-    #     metadata=metas(ShowBehavior.Hide, MergeBehavior.Append, CompareBehavior.Exclude),
-    # )
 
 
 @dataclass
diff --git a/core/dbt/parser/schemas.py b/core/dbt/parser/schemas.py
--- a/core/dbt/parser/schemas.py
+++ b/core/dbt/parser/schemas.py
@@ -714,12 +714,11 @@ def parse(self) -> List[TestBlock]:
         return []
 
     def add_source_definitions(self, source: UnparsedSourceDefinition) -> None:
+        package_name = self.project.project_name
         original_file_path = self.yaml.path.original_file_path
         fqn_path = self.yaml.path.relative_path
         for table in source.tables:
-            unique_id = ".".join(
-                [NodeType.Source, self.project.project_name, source.name, table.name]
-            )
+            unique_id = ".".join([NodeType.Source, package_name, source.name, table.name])
 
             # the FQN is project name / path elements /source_name /table_name
             fqn = self.schema_parser.get_fqn_prefix(fqn_path)
@@ -731,7 +730,7 @@ def add_source_definitions(self, source: UnparsedSourceDefinition) -> None:
                 path=original_file_path,
                 original_file_path=original_file_path,
                 root_path=self.project.project_root,
-                package_name=self.project.project_name,
+                package_name=package_name,
                 unique_id=unique_id,
                 resource_type=NodeType.Source,
                 fqn=fqn,
@@ -1009,6 +1008,8 @@ def parse_exposure(self, unparsed: UnparsedExposure):
             rendered=True,
         )
 
+        config = config.finalize_and_validate()
+
         unrendered_config = self._generate_exposure_config(
             target=unparsed,
             fqn=fqn,
@@ -1111,6 +1112,8 @@ def parse_metric(self, unparsed: UnparsedMetric):
             rendered=True,
         )
 
+        config = config.finalize_and_validate()
+
         unrendered_config = self._generate_metric_config(
             target=unparsed,
             fqn=fqn,
diff --git a/core/dbt/parser/sources.py b/core/dbt/parser/sources.py
--- a/core/dbt/parser/sources.py
+++ b/core/dbt/parser/sources.py
@@ -141,6 +141,8 @@ def parse_source(self, target: UnpatchedSourceDefinition) -> ParsedSourceDefinit
             rendered=True,
         )
 
+        config = config.finalize_and_validate()
+
         unrendered_config = self._generate_source_config(
             target=target,
             rendered=False,


Test patch:
diff --git a/tests/functional/configs/fixtures.py b/tests/functional/configs/fixtures.py
--- a/tests/functional/configs/fixtures.py
+++ b/tests/functional/configs/fixtures.py
@@ -154,6 +154,15 @@
   - name: my_model_3
 """
 
+schema_invalid_enabled_yml = """
+version: 2
+models:
+  - name: my_model
+    config:
+      enabled: True and False
+  - name: my_model_3
+"""
+
 
 class BaseConfigProject:
     @pytest.fixture(scope="class")
diff --git a/tests/functional/configs/test_disabled_model.py b/tests/functional/configs/test_disabled_model.py
--- a/tests/functional/configs/test_disabled_model.py
+++ b/tests/functional/configs/test_disabled_model.py
@@ -1,4 +1,5 @@
 import pytest
+from hologram import ValidationError
 from dbt.tests.util import run_dbt, get_manifest
 
 from dbt.exceptions import CompilationException, ParsingException
@@ -8,6 +9,7 @@
     schema_partial_enabled_yml,
     schema_partial_disabled_yml,
     schema_explicit_enabled_yml,
+    schema_invalid_enabled_yml,
     my_model,
     my_model_2,
     my_model_2_enabled,
@@ -383,3 +385,19 @@ def test_many_disabled_config(self, project):
 
         assert len(manifest.disabled["model.test.my_model_2"]) == 3
         assert len(manifest.disabled["model.test.my_model_3"]) == 3
+
+
+class TestInvalidEnabledConfig:
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "schema.yml": schema_invalid_enabled_yml,
+            "my_model.sql": my_model,
+        }
+
+    def test_invalis_config(self, project):
+        with pytest.raises(ValidationError) as exc:
+            run_dbt(["parse"])
+        exc_str = " ".join(str(exc.value).split())  # flatten all whitespace
+        expected_msg = "'True and False' is not of type 'boolean'"
+        assert expected_msg in exc_str
diff --git a/tests/functional/exposures/fixtures.py b/tests/functional/exposures/fixtures.py
new file mode 100644
--- /dev/null
+++ b/tests/functional/exposures/fixtures.py
@@ -0,0 +1,114 @@
+
+models_sql = """
+select 1 as id
+"""
+
+second_model_sql = """
+select 1 as id
+"""
+
+simple_exposure_yml = """
+version: 2
+
+exposures:
+  - name: simple_exposure
+    label: simple exposure label
+    type: dashboard
+    depends_on:
+      - ref('model')
+    owner:
+      email: something@example.com
+  - name: notebook_exposure
+    type: notebook
+    depends_on:
+      - ref('model')
+      - ref('second_model')
+    owner:
+      email: something@example.com
+      name: Some name
+    description: >
+      A description of the complex exposure
+    maturity: medium
+    meta:
+      tool: 'my_tool'
+      languages:
+        - python
+    tags: ['my_department']
+    url: http://example.com/notebook/1
+"""
+
+disabled_models_exposure_yml = """
+version: 2
+
+exposures:
+  - name: simple_exposure
+    type: dashboard
+    config:
+      enabled: False
+    depends_on:
+      - ref('model')
+    owner:
+      email: something@example.com
+  - name: notebook_exposure
+    type: notebook
+    depends_on:
+      - ref('model')
+      - ref('second_model')
+    owner:
+      email: something@example.com
+      name: Some name
+    description: >
+      A description of the complex exposure
+    maturity: medium
+    meta:
+      tool: 'my_tool'
+      languages:
+        - python
+    tags: ['my_department']
+    url: http://example.com/notebook/1
+"""
+
+enabled_yaml_level_exposure_yml = """
+version: 2
+
+exposures:
+  - name: simple_exposure
+    type: dashboard
+    config:
+      enabled: True
+    depends_on:
+      - ref('model')
+    owner:
+      email: something@example.com
+  - name: notebook_exposure
+    type: notebook
+    depends_on:
+      - ref('model')
+      - ref('second_model')
+    owner:
+      email: something@example.com
+      name: Some name
+    description: >
+      A description of the complex exposure
+    maturity: medium
+    meta:
+      tool: 'my_tool'
+      languages:
+        - python
+    tags: ['my_department']
+    url: http://example.com/notebook/1
+"""
+
+invalid_config_exposure_yml = """
+version: 2
+
+exposures:
+  - name: simple_exposure
+    type: dashboard
+    config:
+      enabled: True and False
+    depends_on:
+      - ref('model')
+    owner:
+      email: something@example.com
+"""
diff --git a/tests/functional/exposures/test_exposure_configs.py b/tests/functional/exposures/test_exposure_configs.py
--- a/tests/functional/exposures/test_exposure_configs.py
+++ b/tests/functional/exposures/test_exposure_configs.py
@@ -1,7 +1,17 @@
 import pytest
+from hologram import ValidationError
+
 from dbt.contracts.graph.model_config import ExposureConfig
 
 from dbt.tests.util import run_dbt, update_config_file, get_manifest
+from tests.functional.exposures.fixtures import (
+    models_sql,
+    second_model_sql,
+    simple_exposure_yml,
+    disabled_models_exposure_yml,
+    enabled_yaml_level_exposure_yml,
+    invalid_config_exposure_yml
+)
 
 
 class ExposureConfigTests:
@@ -12,45 +22,6 @@ def setUp(self):
         )
 
 
-exposure_yml = """
-version: 2
-
-exposures:
-  - name: simple_exposure
-    type: dashboard
-    depends_on:
-      - ref('model')
-    owner:
-      email: something@example.com
-  - name: notebook_exposure
-    type: notebook
-    depends_on:
-      - ref('model')
-      - ref('second_model')
-    owner:
-      email: something@example.com
-      name: Some name
-    description: >
-      A description of the complex exposure
-    maturity: medium
-    meta:
-      tool: 'my_tool'
-      languages:
-        - python
-    tags: ['my_department']
-    url: http://example.com/notebook/1
-"""
-
-
-models_sql = """
-select 1 as id
-"""
-
-second_model_sql = """
-select 1 as id
-"""
-
-
 # Test enabled config for exposure in dbt_project.yml
 class TestExposureEnabledConfigProjectLevel(ExposureConfigTests):
     @pytest.fixture(scope="class")
@@ -58,7 +29,7 @@ def models(self):
         return {
             "model.sql": models_sql,
             "second_model.sql": second_model_sql,
-            "schema.yml": exposure_yml,
+            "schema.yml": simple_exposure_yml,
         }
 
     @pytest.fixture(scope="class")
@@ -92,38 +63,6 @@ def test_enabled_exposure_config_dbt_project(self, project):
         assert "exposure.test.notebook_exposure" in manifest.exposures
 
 
-disabled_models_exposure_yml = """
-version: 2
-
-exposures:
-  - name: simple_exposure
-    type: dashboard
-    config:
-      enabled: False
-    depends_on:
-      - ref('model')
-    owner:
-      email: something@example.com
-  - name: notebook_exposure
-    type: notebook
-    depends_on:
-      - ref('model')
-      - ref('second_model')
-    owner:
-      email: something@example.com
-      name: Some name
-    description: >
-      A description of the complex exposure
-    maturity: medium
-    meta:
-      tool: 'my_tool'
-      languages:
-        - python
-    tags: ['my_department']
-    url: http://example.com/notebook/1
-"""
-
-
 # Test disabled config at exposure level in yml file
 class TestConfigYamlLevel(ExposureConfigTests):
     @pytest.fixture(scope="class")
@@ -141,40 +80,8 @@ def test_exposure_config_yaml_level(self, project):
         assert "exposure.test.notebook_exposure" in manifest.exposures
 
 
-enabled_yaml_level_exposure_yml = """
-version: 2
-
-exposures:
-  - name: simple_exposure
-    type: dashboard
-    config:
-      enabled: True
-    depends_on:
-      - ref('model')
-    owner:
-      email: something@example.com
-  - name: notebook_exposure
-    type: notebook
-    depends_on:
-      - ref('model')
-      - ref('second_model')
-    owner:
-      email: something@example.com
-      name: Some name
-    description: >
-      A description of the complex exposure
-    maturity: medium
-    meta:
-      tool: 'my_tool'
-      languages:
-        - python
-    tags: ['my_department']
-    url: http://example.com/notebook/1
-"""
-
-
 # Test inheritence - set configs at project and exposure level - expect exposure level to win
-class TestExposureConfigsInheritence1(ExposureConfigTests):
+class TestExposureConfigsInheritence(ExposureConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
         return {
@@ -199,3 +106,20 @@ def test_exposure_all_configs(self, project):
 
         assert isinstance(config_test_table, ExposureConfig)
         assert config_test_table == pytest.expected_config
+
+
+# Test invalid config triggers error
+class TestInvalidConfig(ExposureConfigTests):
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "model.sql": models_sql,
+            "second_model.sql": second_model_sql,
+            "schema.yml": invalid_config_exposure_yml,
+        }
+
+    def test_exposure_config_yaml_level(self, project):
+        with pytest.raises(ValidationError) as excinfo:
+            run_dbt(["parse"])
+        expected_msg = "'True and False' is not of type 'boolean'"
+        assert expected_msg in str(excinfo.value)
diff --git a/tests/functional/exposures/test_exposures.py b/tests/functional/exposures/test_exposures.py
--- a/tests/functional/exposures/test_exposures.py
+++ b/tests/functional/exposures/test_exposures.py
@@ -1,52 +1,18 @@
 import pytest
 
 from dbt.tests.util import run_dbt, get_manifest
-
-
-models_sql = """
-select 1 as id
-"""
-
-second_model_sql = """
-select 1 as id
-"""
-
-names_valid_yml = """
-version: 2
-
-exposures:
-  - name: simple_exposure
-    label: simple exposure label
-    type: dashboard
-    depends_on:
-      - ref('model')
-    owner:
-      email: something@example.com
-  - name: notebook_exposure
-    type: notebook
-    depends_on:
-      - ref('model')
-      - ref('second_model')
-    owner:
-      email: something@example.com
-      name: Some name
-    description: >
-      A description of the complex exposure
-    maturity: medium
-    meta:
-      tool: 'my_tool'
-      languages:
-        - python
-    tags: ['my_department']
-    url: http://example.com/notebook/1
-"""
+from tests.functional.exposures.fixtures import (
+    models_sql,
+    second_model_sql,
+    simple_exposure_yml,
+)
 
 
 class TestBasicExposures:
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "exposure.yml": names_valid_yml,
+            "exposure.yml": simple_exposure_yml,
             "model.sql": models_sql,
             "second_model.sql": second_model_sql,
         }
diff --git a/tests/functional/metrics/fixture_metrics.py b/tests/functional/metrics/fixture_metrics.py
deleted file mode 100644
--- a/tests/functional/metrics/fixture_metrics.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# not strictly necessary, but this reflects the integration tests currently in the 'dbt-metrics' package right now
-# i'm including just the first 10 rows for more concise 'git diff'
-
-mock_purchase_data_csv = """purchased_at,payment_type,payment_total
-2021-02-14 17:52:36,maestro,2418.94
-2021-02-15 04:16:50,jcb,3043.28
-2021-02-15 11:30:45,solo,1505.81
-2021-02-16 13:08:18,,1532.85
-2021-02-17 05:41:34,americanexpress,319.91
-2021-02-18 06:47:32,jcb,2143.44
-2021-02-19 01:37:09,jcb,840.1
-2021-02-19 03:38:49,jcb,1388.18
-2021-02-19 04:22:41,jcb,2834.96
-2021-02-19 13:28:50,china-unionpay,2440.98
-""".strip()
-
-models__people_sql = """
-select 1 as id, 'Drew' as first_name, 'Banin' as last_name, 'yellow' as favorite_color, true as loves_dbt, 5 as tenure, current_timestamp as created_at
-union all
-select 1 as id, 'Jeremy' as first_name, 'Cohen' as last_name, 'indigo' as favorite_color, true as loves_dbt, 4 as tenure, current_timestamp as created_at
-union all
-select 1 as id, 'Callum' as first_name, 'McCann' as last_name, 'emerald' as favorite_color, true as loves_dbt, 0 as tenure, current_timestamp as created_at
-"""
diff --git a/tests/functional/metrics/fixtures.py b/tests/functional/metrics/fixtures.py
new file mode 100644
--- /dev/null
+++ b/tests/functional/metrics/fixtures.py
@@ -0,0 +1,644 @@
+# not strictly necessary, but this reflects the integration tests currently in the 'dbt-metrics' package right now
+# i'm including just the first 10 rows for more concise 'git diff'
+
+mock_purchase_data_csv = """purchased_at,payment_type,payment_total
+2021-02-14 17:52:36,maestro,2418.94
+2021-02-15 04:16:50,jcb,3043.28
+2021-02-15 11:30:45,solo,1505.81
+2021-02-16 13:08:18,,1532.85
+2021-02-17 05:41:34,americanexpress,319.91
+2021-02-18 06:47:32,jcb,2143.44
+2021-02-19 01:37:09,jcb,840.1
+2021-02-19 03:38:49,jcb,1388.18
+2021-02-19 04:22:41,jcb,2834.96
+2021-02-19 13:28:50,china-unionpay,2440.98
+""".strip()
+
+models_people_sql = """
+select 1 as id, 'Drew' as first_name, 'Banin' as last_name, 'yellow' as favorite_color, true as loves_dbt, 5 as tenure, current_timestamp as created_at
+union all
+select 1 as id, 'Jeremy' as first_name, 'Cohen' as last_name, 'indigo' as favorite_color, true as loves_dbt, 4 as tenure, current_timestamp as created_at
+union all
+select 1 as id, 'Callum' as first_name, 'McCann' as last_name, 'emerald' as favorite_color, true as loves_dbt, 0 as tenure, current_timestamp as created_at
+"""
+
+basic_metrics_yml = """
+version: 2
+
+metrics:
+
+  - name: number_of_people
+    label: "Number of people"
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+  - name: collective_tenure
+    label: "Collective tenure"
+    description: Total number of years of team experience
+    model: "ref('people')"
+    calculation_method: sum
+    expression: tenure
+    timestamp: created_at
+    time_grains: [day, week, month]
+    filters:
+      - field: loves_dbt
+        operator: 'is'
+        value: 'true'
+
+  - name: average_tenure
+    label: "Average tenure"
+    description: "The average tenure per person"
+    calculation_method: derived
+    expression: "{{metric('collective_tenure')}} / {{metric('number_of_people')}} "
+    timestamp: created_at
+    time_grains: [day, week, month]
+
+  - name: average_tenure_plus_one
+    label: "Average tenure"
+    description: "The average tenure per person"
+    calculation_method: derived
+    expression: "{{metric('average_tenure')}} + 1 "
+    timestamp: created_at
+    time_grains: [day, week, month]
+"""
+
+models_people_metrics_yml = """
+version: 2
+
+metrics:
+
+  - name: number_of_people
+    label: "Number of people"
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+  - name: collective_tenure
+    label: "Collective tenure"
+    description: Total number of years of team experience
+    model: "ref('people')"
+    calculation_method: sum
+    expression: tenure
+    timestamp: created_at
+    time_grains: [day]
+    filters:
+      - field: loves_dbt
+        operator: 'is'
+        value: 'true'
+
+  - name: collective_window
+    label: "Collective window"
+    description: Testing window
+    model: "ref('people')"
+    calculation_method: sum
+    expression: tenure
+    timestamp: created_at
+    time_grains: [day]
+    window:
+      count: 14
+      period: day
+    filters:
+      - field: loves_dbt
+        operator: 'is'
+        value: 'true'
+
+"""
+
+invalid_models_people_metrics_yml = """
+version: 2
+
+metrics:
+
+  - name: number_of_people
+    label: "Number of people"
+    description: Total count of people
+    model: "ref(people)"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+  - name: collective_tenure
+    label: "Collective tenure"
+    description: Total number of years of team experience
+    model: "ref(people)"
+    calculation_method: sum
+    expression: tenure
+    timestamp: created_at
+    time_grains: [day]
+    filters:
+      - field: loves_dbt
+        operator: 'is'
+        value: 'true'
+
+"""
+
+invalid_metrics_missing_model_yml = """
+version: 2
+
+metrics:
+
+  - name: number_of_people
+    label: "Number of people"
+    description: Total count of people
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+  - name: collective_tenure
+    label: "Collective tenure"
+    description: Total number of years of team experience
+    calculation_method: sum
+    expression: tenure
+    timestamp: created_at
+    time_grains: [day]
+    filters:
+      - field: loves_dbt
+        operator: 'is'
+        value: 'true'
+
+"""
+
+invalid_metrics_missing_expression_yml = """
+version: 2
+metrics:
+  - name: number_of_people
+    label: "Number of people"
+    model: "ref(people)"
+    description: Total count of people
+    calculation_method: count
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+"""
+
+names_with_spaces_metrics_yml = """
+version: 2
+
+metrics:
+
+  - name: number of people
+    label: "Number of people"
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+"""
+
+names_with_special_chars_metrics_yml = """
+version: 2
+
+metrics:
+
+  - name: number_of_people!
+    label: "Number of people"
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+"""
+
+
+names_with_leading_numeric_metrics_yml = """
+version: 2
+
+metrics:
+
+  - name: 1_number_of_people
+    label: "Number of people"
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+"""
+
+long_name_metrics_yml = """
+version: 2
+
+metrics:
+
+  - name: this_name_is_going_to_contain_more_than_250_characters_but_be_otherwise_acceptable_and_then_will_throw_an_error_which_I_expect_to_happen_and_repeat_this_name_is_going_to_contain_more_than_250_characters_but_be_otherwise_acceptable_and_then_will_throw_an_error_which_I_expect_to_happen
+    label: "Number of people"
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+"""
+
+downstream_model_sql = """
+-- this model will depend on these three metrics
+{% set some_metrics = [
+    metric('count_orders'),
+    metric('sum_order_revenue'),
+    metric('average_order_value')
+] %}
+
+/*
+{% if not execute %}
+
+    -- the only properties available to us at 'parse' time are:
+    --      'metric_name'
+    --      'package_name' (None if same package)
+
+    {% set metric_names = [] %}
+    {% for m in some_metrics %}
+        {% do metric_names.append(m.metric_name) %}
+    {% endfor %}
+
+    -- this config does nothing, but it lets us check these values below
+    {{ config(metric_names = metric_names) }}
+
+{% else %}
+
+    -- these are the properties available to us at 'execution' time
+
+    {% for m in some_metrics %}
+        name: {{ m.name }}
+        label: {{ m.label }}
+        calculation_method: {{ m.calculation_method }}
+        expression: {{ m.expression }}
+        timestamp: {{ m.timestamp }}
+        time_grains: {{ m.time_grains }}
+        dimensions: {{ m.dimensions }}
+        filters: {{ m.filters }}
+        window: {{ m.window }}
+    {% endfor %}
+
+{% endif %}
+
+select 1 as id
+"""
+
+invalid_derived_metric_contains_model_yml = """
+version: 2
+metrics:
+    - name: count_orders
+      label: Count orders
+      model: ref('mock_purchase_data')
+
+      calculation_method: count
+      expression: "*"
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+
+    - name: sum_order_revenue
+      label: Total order revenue
+      model: ref('mock_purchase_data')
+
+      calculation_method: sum
+      expression: "payment_total"
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+
+    - name: average_order_value
+      label: Average Order Value
+
+      calculation_method: derived
+      expression:  "{{metric('sum_order_revenue')}} / {{metric('count_orders')}} "
+      model: ref('mock_purchase_data')
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+"""
+
+derived_metric_yml = """
+version: 2
+metrics:
+    - name: count_orders
+      label: Count orders
+      model: ref('mock_purchase_data')
+
+      calculation_method: count
+      expression: "*"
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+
+    - name: sum_order_revenue
+      label: Total order revenue
+      model: ref('mock_purchase_data')
+
+      calculation_method: sum
+      expression: "payment_total"
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+
+    - name: average_order_value
+      label: Average Order Value
+
+      calculation_method: derived
+      expression:  "{{metric('sum_order_revenue')}} / {{metric('count_orders')}} "
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+"""
+
+derived_metric_old_attr_names_yml = """
+version: 2
+metrics:
+    - name: count_orders
+      label: Count orders
+      model: ref('mock_purchase_data')
+
+      type: count
+      sql: "*"
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+
+    - name: sum_order_revenue
+      label: Total order revenue
+      model: ref('mock_purchase_data')
+
+      type: sum
+      sql: "payment_total"
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+
+    - name: average_order_value
+      label: Average Order Value
+
+      type: expression
+      sql:  "{{metric('sum_order_revenue')}} / {{metric('count_orders')}} "
+      timestamp: purchased_at
+      time_grains: [day, week, month, quarter, year]
+
+      dimensions:
+        - payment_type
+"""
+
+disabled_metric_level_schema_yml = """
+version: 2
+
+metrics:
+
+  - name: number_of_people
+    label: "Number of people"
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    config:
+      enabled: False
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+  - name: collective_tenure
+    label: "Collective tenure"
+    description: Total number of years of team experience
+    model: "ref('people')"
+    calculation_method: sum
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day]
+    filters:
+      - field: loves_dbt
+        operator: 'is'
+        value: 'true'
+
+"""
+
+enabled_metric_level_schema_yml = """
+version: 2
+
+metrics:
+
+  - name: number_of_people
+    label: "Number of people"
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    config:
+      enabled: True
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+
+  - name: collective_tenure
+    label: "Collective tenure"
+    description: Total number of years of team experience
+    model: "ref('people')"
+    calculation_method: sum
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day]
+    filters:
+      - field: loves_dbt
+        operator: 'is'
+        value: 'true'
+
+"""
+
+models_people_metrics_sql = """
+-- this model will depend on these two metrics
+{% set some_metrics = [
+    metric('number_of_people'),
+    metric('collective_tenure')
+] %}
+
+/*
+{% if not execute %}
+
+    -- the only properties available to us at 'parse' time are:
+    --      'metric_name'
+    --      'package_name' (None if same package)
+
+    {% set metric_names = [] %}
+    {% for m in some_metrics %}
+        {% do metric_names.append(m.metric_name) %}
+    {% endfor %}
+
+    -- this config does nothing, but it lets us check these values below
+    {{ config(metric_names = metric_names) }}
+
+{% else %}
+
+    -- these are the properties available to us at 'execution' time
+
+    {% for m in some_metrics %}
+        name: {{ m.name }}
+        label: {{ m.label }}
+        calculation_method: {{ m.calculation_method }}
+        expression: {{ m.expression }}
+        timestamp: {{ m.timestamp }}
+        time_grains: {{ m.time_grains }}
+        dimensions: {{ m.dimensions }}
+        filters: {{ m.filters }}
+        window: {{ m.window }}
+    {% endfor %}
+
+{% endif %}
+
+select 1 as id
+"""
+
+metrics_1_yml = """
+version: 2
+
+metrics:
+  - name: some_metric
+    label: Some Metric
+    model: ref('model_a')
+
+    calculation_method: count
+    expression: id
+
+    timestamp: ts
+    time_grains: [day]
+"""
+
+metrics_2_yml = """
+version: 2
+
+metrics:
+  - name: some_metric
+    label: Some Metric
+    model: ref('model_a')
+
+    calculation_method: count
+    expression: user_id
+
+    timestamp: ts
+    time_grains: [day]
+"""
+
+model_a_sql = """
+select 1 as fun
+"""
+
+model_b_sql = """
+-- {{ metric('some_metric') }}
+
+{% if execute %}
+  {% set model_ref_node = graph.nodes.values() | selectattr('name', 'equalto', 'model_a') | first %}
+  {% set relation = api.Relation.create(
+      database = model_ref_node.database,
+      schema = model_ref_node.schema,
+      identifier = model_ref_node.alias
+  )
+  %}
+{% else %}
+  {% set relation = "" %}
+{% endif %}
+
+-- this one is a real ref
+select * from {{ ref('model_a') }}
+union all
+-- this one is synthesized via 'graph' var
+select * from {{ relation }}
+"""
+
+invalid_config_metric_yml = """
+version: 2
+
+metrics:
+  - name: number_of_people
+    label: "Number of people"
+    config:
+        enabled: True and False
+    description: Total count of people
+    model: "ref('people')"
+    calculation_method: count
+    expression: "*"
+    timestamp: created_at
+    time_grains: [day, week, month]
+    dimensions:
+      - favorite_color
+      - loves_dbt
+    meta:
+        my_meta: 'testing'
+"""
diff --git a/tests/functional/metrics/test_metric_configs.py b/tests/functional/metrics/test_metric_configs.py
--- a/tests/functional/metrics/test_metric_configs.py
+++ b/tests/functional/metrics/test_metric_configs.py
@@ -1,10 +1,18 @@
 import pytest
+from hologram import ValidationError
 from dbt.contracts.graph.model_config import MetricConfig
 from dbt.exceptions import CompilationException
 from dbt.tests.util import run_dbt, update_config_file, get_manifest
 
 
-from tests.functional.metrics.fixture_metrics import models__people_sql
+from tests.functional.metrics.fixtures import (
+    models_people_sql,
+    models_people_metrics_yml,
+    disabled_metric_level_schema_yml,
+    enabled_metric_level_schema_yml,
+    models_people_metrics_sql,
+    invalid_config_metric_yml
+)
 
 
 class MetricConfigTests:
@@ -15,48 +23,13 @@ def setUp(self):
         )
 
 
-models__people_metrics_yml = """
-version: 2
-
-metrics:
-
-  - name: number_of_people
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-  - name: collective_tenure
-    label: "Collective tenure"
-    description: Total number of years of team experience
-    model: "ref('people')"
-    calculation_method: sum
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day]
-    filters:
-      - field: loves_dbt
-        operator: 'is'
-        value: 'true'
-
-"""
-
-
 # Test enabled config in dbt_project.yml
 class TestMetricEnabledConfigProjectLevel(MetricConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "people.sql": models__people_sql,
-            "schema.yml": models__people_metrics_yml,
+            "people.sql": models_people_sql,
+            "schema.yml": models_people_metrics_yml,
         }
 
     @pytest.fixture(scope="class")
@@ -90,50 +63,13 @@ def test_enabled_metric_config_dbt_project(self, project):
         assert "metric.test.collective_tenure" in manifest.metrics
 
 
-disabled_metric_level__schema_yml = """
-version: 2
-
-metrics:
-
-  - name: number_of_people
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    config:
-      enabled: False
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-  - name: collective_tenure
-    label: "Collective tenure"
-    description: Total number of years of team experience
-    model: "ref('people')"
-    calculation_method: sum
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day]
-    filters:
-      - field: loves_dbt
-        operator: 'is'
-        value: 'true'
-
-"""
-
-
 # Test enabled config at metrics level in yml file
 class TestConfigYamlMetricLevel(MetricConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "people.sql": models__people_sql,
-            "schema.yml": disabled_metric_level__schema_yml,
+            "people.sql": models_people_sql,
+            "schema.yml": disabled_metric_level_schema_yml,
         }
 
     def test_metric_config_yaml_metric_level(self, project):
@@ -143,50 +79,13 @@ def test_metric_config_yaml_metric_level(self, project):
         assert "metric.test.collective_tenure" in manifest.metrics
 
 
-enabled_metric_level__schema_yml = """
-version: 2
-
-metrics:
-
-  - name: number_of_people
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    config:
-      enabled: True
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-  - name: collective_tenure
-    label: "Collective tenure"
-    description: Total number of years of team experience
-    model: "ref('people')"
-    calculation_method: sum
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day]
-    filters:
-      - field: loves_dbt
-        operator: 'is'
-        value: 'true'
-
-"""
-
-
 # Test inheritence - set configs at project and metric level - expect metric level to win
 class TestMetricConfigsInheritence(MetricConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "people.sql": models__people_sql,
-            "schema.yml": enabled_metric_level__schema_yml,
+            "people.sql": models_people_sql,
+            "schema.yml": enabled_metric_level_schema_yml,
         }
 
     @pytest.fixture(scope="class")
@@ -207,58 +106,14 @@ def test_metrics_all_configs(self, project):
         assert config_test_table == pytest.expected_config
 
 
-models__people_metrics_sql = """
--- this model will depend on these two metrics
-{% set some_metrics = [
-    metric('number_of_people'),
-    metric('collective_tenure')
-] %}
-
-/*
-{% if not execute %}
-
-    -- the only properties available to us at 'parse' time are:
-    --      'metric_name'
-    --      'package_name' (None if same package)
-
-    {% set metric_names = [] %}
-    {% for m in some_metrics %}
-        {% do metric_names.append(m.metric_name) %}
-    {% endfor %}
-
-    -- this config does nothing, but it lets us check these values below
-    {{ config(metric_names = metric_names) }}
-
-{% else %}
-
-    -- these are the properties available to us at 'execution' time
-
-    {% for m in some_metrics %}
-        name: {{ m.name }}
-        label: {{ m.label }}
-        calculation_method: {{ m.calculation_method }}
-        expression: {{ m.expression }}
-        timestamp: {{ m.timestamp }}
-        time_grains: {{ m.time_grains }}
-        dimensions: {{ m.dimensions }}
-        filters: {{ m.filters }}
-        window: {{ m.window }}
-    {% endfor %}
-
-{% endif %}
-
-select 1 as id
-"""
-
-
 # Test CompilationException if a model references a disabled metric
 class TestDisabledMetricRef(MetricConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "people.sql": models__people_sql,
-            "people_metrics.sql": models__people_metrics_sql,
-            "schema.yml": models__people_metrics_yml,
+            "people.sql": models_people_sql,
+            "people_metrics.sql": models_people_metrics_sql,
+            "schema.yml": models_people_metrics_yml,
         }
 
     def test_disabled_metric_ref_model(self, project):
@@ -281,3 +136,19 @@ def test_disabled_metric_ref_model(self, project):
         update_config_file(new_enabled_config, project.project_root, "dbt_project.yml")
         with pytest.raises(CompilationException):
             run_dbt(["parse"])
+
+
+# Test invalid metric configs
+class TestInvalidMetric(MetricConfigTests):
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "people.sql": models_people_sql,
+            "schema.yml": invalid_config_metric_yml,
+        }
+
+    def test_invalid_config_metric(self, project):
+        with pytest.raises(ValidationError) as excinfo:
+            run_dbt(["parse"])
+        expected_msg = "'True and False' is not of type 'boolean'"
+        assert expected_msg in str(excinfo.value)
diff --git a/tests/functional/metrics/test_metric_deferral.py b/tests/functional/metrics/test_metric_deferral.py
--- a/tests/functional/metrics/test_metric_deferral.py
+++ b/tests/functional/metrics/test_metric_deferral.py
@@ -2,62 +2,12 @@
 import pytest
 from dbt.tests.util import run_dbt, copy_file, write_file
 from pathlib import Path
-
-metrics_yml = """
-version: 2
-
-metrics:
-  - name: some_metric
-    label: Some Metric
-    model: ref('model_a')
-
-    calculation_method: count
-    expression: id
-
-    timestamp: ts
-    time_grains: [day]
-"""
-
-metrics_2_yml = """
-version: 2
-
-metrics:
-  - name: some_metric
-    label: Some Metric
-    model: ref('model_a')
-
-    calculation_method: count
-    expression: user_id
-
-    timestamp: ts
-    time_grains: [day]
-"""
-
-model_a_sql = """
-select 1 as fun
-"""
-
-model_b_sql = """
--- {{ metric('some_metric') }}
-
-{% if execute %}
-  {% set model_ref_node = graph.nodes.values() | selectattr('name', 'equalto', 'model_a') | first %}
-  {% set relation = api.Relation.create(
-      database = model_ref_node.database,
-      schema = model_ref_node.schema,
-      identifier = model_ref_node.alias
-  )
-  %}
-{% else %}
-  {% set relation = "" %}
-{% endif %}
-
--- this one is a real ref
-select * from {{ ref('model_a') }}
-union all
--- this one is synthesized via 'graph' var
-select * from {{ relation }}
-"""
+from tests.functional.metrics.fixtures import (
+    metrics_1_yml,
+    metrics_2_yml,
+    model_a_sql,
+    model_b_sql,
+)
 
 
 class TestMetricDeferral:
@@ -106,7 +56,7 @@ def models(self):
         return {
             "model_a.sql": model_a_sql,
             "model_b.sql": model_b_sql,
-            "metrics.yml": metrics_yml,
+            "metrics.yml": metrics_1_yml,
         }
 
     def test_metric_deferral(self, project):
diff --git a/tests/functional/metrics/test_metric_helper_functions.py b/tests/functional/metrics/test_metric_helper_functions.py
--- a/tests/functional/metrics/test_metric_helper_functions.py
+++ b/tests/functional/metrics/test_metric_helper_functions.py
@@ -3,64 +3,18 @@
 from dbt.tests.util import run_dbt, get_manifest
 from dbt.contracts.graph.metrics import ResolvedMetricReference
 
-from tests.functional.metrics.fixture_metrics import models__people_sql
-
-metrics__yml = """
-version: 2
-
-metrics:
-
-  - name: number_of_people
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-  - name: collective_tenure
-    label: "Collective tenure"
-    description: Total number of years of team experience
-    model: "ref('people')"
-    calculation_method: sum
-    expression: tenure
-    timestamp: created_at
-    time_grains: [day, week, month]
-    filters:
-      - field: loves_dbt
-        operator: 'is'
-        value: 'true'
-
-  - name: average_tenure
-    label: "Average tenure"
-    description: "The average tenure per person"
-    calculation_method: derived
-    expression: "{{metric('collective_tenure')}} / {{metric('number_of_people')}} "
-    timestamp: created_at
-    time_grains: [day, week, month]
-
-  - name: average_tenure_plus_one
-    label: "Average tenure"
-    description: "The average tenure per person"
-    calculation_method: derived
-    expression: "{{metric('average_tenure')}} + 1 "
-    timestamp: created_at
-    time_grains: [day, week, month]
-"""
+from tests.functional.metrics.fixtures import (
+    models_people_sql,
+    basic_metrics_yml
+)
 
 
 class TestMetricHelperFunctions:
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "metrics.yml": metrics__yml,
-            "people.sql": models__people_sql,
+            "metrics.yml": basic_metrics_yml,
+            "people.sql": models_people_sql,
         }
 
     def test_expression_metric(
diff --git a/tests/functional/metrics/test_metrics.py b/tests/functional/metrics/test_metrics.py
--- a/tests/functional/metrics/test_metrics.py
+++ b/tests/functional/metrics/test_metrics.py
@@ -3,66 +3,30 @@
 from dbt.tests.util import run_dbt, get_manifest
 from dbt.exceptions import ParsingException
 
-from tests.functional.metrics.fixture_metrics import mock_purchase_data_csv, models__people_sql
-
-
-models__people_metrics_yml = """
-version: 2
-
-metrics:
-
-  - name: number_of_people
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-  - name: collective_tenure
-    label: "Collective tenure"
-    description: Total number of years of team experience
-    model: "ref('people')"
-    calculation_method: sum
-    expression: tenure
-    timestamp: created_at
-    time_grains: [day]
-    filters:
-      - field: loves_dbt
-        operator: 'is'
-        value: 'true'
-
-  - name: collective_window
-    label: "Collective window"
-    description: Testing window
-    model: "ref('people')"
-    calculation_method: sum
-    expression: tenure
-    timestamp: created_at
-    time_grains: [day]
-    window:
-      count: 14
-      period: day
-    filters:
-      - field: loves_dbt
-        operator: 'is'
-        value: 'true'
-
-"""
+from tests.functional.metrics.fixtures import (
+    mock_purchase_data_csv,
+    models_people_sql,
+    models_people_metrics_yml,
+    invalid_models_people_metrics_yml,
+    invalid_metrics_missing_model_yml,
+    invalid_metrics_missing_expression_yml,
+    names_with_spaces_metrics_yml,
+    names_with_special_chars_metrics_yml,
+    names_with_leading_numeric_metrics_yml,
+    long_name_metrics_yml,
+    downstream_model_sql,
+    invalid_derived_metric_contains_model_yml,
+    derived_metric_yml,
+    derived_metric_old_attr_names_yml,
+)
 
 
 class TestSimpleMetrics:
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "people_metrics.yml": models__people_metrics_yml,
-            "people.sql": models__people_sql,
+            "people_metrics.yml": models_people_metrics_yml,
+            "people.sql": models_people_sql,
         }
 
     def test_simple_metric(
@@ -82,47 +46,12 @@ def test_simple_metric(
         assert metric_ids == expected_metric_ids
 
 
-invalid_models__people_metrics_yml = """
-version: 2
-
-metrics:
-
-  - name: number_of_people
-    label: "Number of people"
-    description: Total count of people
-    model: "ref(people)"
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-  - name: collective_tenure
-    label: "Collective tenure"
-    description: Total number of years of team experience
-    model: "ref(people)"
-    calculation_method: sum
-    expression: tenure
-    timestamp: created_at
-    time_grains: [day]
-    filters:
-      - field: loves_dbt
-        operator: 'is'
-        value: 'true'
-
-"""
-
-
 class TestInvalidRefMetrics:
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "people_metrics.yml": invalid_models__people_metrics_yml,
-            "people.sql": models__people_sql,
+            "people_metrics.yml": invalid_models_people_metrics_yml,
+            "people.sql": models_people_sql,
         }
 
     # tests that we get a ParsingException with an invalid model ref, where
@@ -136,45 +65,12 @@ def test_simple_metric(
             run_dbt(["run"])
 
 
-invalid_metrics__missing_model_yml = """
-version: 2
-
-metrics:
-
-  - name: number_of_people
-    label: "Number of people"
-    description: Total count of people
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-  - name: collective_tenure
-    label: "Collective tenure"
-    description: Total number of years of team experience
-    calculation_method: sum
-    expression: tenure
-    timestamp: created_at
-    time_grains: [day]
-    filters:
-      - field: loves_dbt
-        operator: 'is'
-        value: 'true'
-
-"""
-
-
 class TestInvalidMetricMissingModel:
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "people_metrics.yml": invalid_metrics__missing_model_yml,
-            "people.sql": models__people_sql,
+            "people_metrics.yml": invalid_metrics_missing_model_yml,
+            "people.sql": models_people_sql,
         }
 
     # tests that we get a ParsingException with an invalid model ref, where
@@ -188,30 +84,12 @@ def test_simple_metric(
             run_dbt(["run"])
 
 
-invalid_metrics__missing_expression_yml = """
-version: 2
-metrics:
-  - name: number_of_people
-    label: "Number of people"
-    model: "ref(people)"
-    description: Total count of people
-    calculation_method: count
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-"""
-
-
 class TestInvalidMetricMissingExpression:
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "people_metrics.yml": invalid_metrics__missing_expression_yml,
-            "people.sql": models__people_sql,
+            "people_metrics.yml": invalid_metrics_missing_expression_yml,
+            "people.sql": models_people_sql,
         }
 
     # tests that we get a ParsingException with a missing expression
@@ -224,34 +102,12 @@ def test_simple_metric(
             run_dbt(["run"])
 
 
-names_with_spaces_metrics_yml = """
-version: 2
-
-metrics:
-
-  - name: number of people
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-"""
-
-
 class TestNamesWithSpaces:
     @pytest.fixture(scope="class")
     def models(self):
         return {
             "people_metrics.yml": names_with_spaces_metrics_yml,
-            "people.sql": models__people_sql,
+            "people.sql": models_people_sql,
         }
 
     def test_names_with_spaces(self, project):
@@ -260,34 +116,12 @@ def test_names_with_spaces(self, project):
         assert "cannot contain spaces" in str(exc.value)
 
 
-names_with_special_chars_metrics_yml = """
-version: 2
-
-metrics:
-
-  - name: number_of_people!
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-"""
-
-
 class TestNamesWithSpecialChar:
     @pytest.fixture(scope="class")
     def models(self):
         return {
             "people_metrics.yml": names_with_special_chars_metrics_yml,
-            "people.sql": models__people_sql,
+            "people.sql": models_people_sql,
         }
 
     def test_names_with_special_char(self, project):
@@ -296,34 +130,12 @@ def test_names_with_special_char(self, project):
         assert "must contain only letters, numbers and underscores" in str(exc.value)
 
 
-names_with_leading_numeric_metrics_yml = """
-version: 2
-
-metrics:
-
-  - name: 1_number_of_people
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-"""
-
-
 class TestNamesWithLeandingNumber:
     @pytest.fixture(scope="class")
     def models(self):
         return {
             "people_metrics.yml": names_with_leading_numeric_metrics_yml,
-            "people.sql": models__people_sql,
+            "people.sql": models_people_sql,
         }
 
     def test_names_with_leading_number(self, project):
@@ -332,34 +144,12 @@ def test_names_with_leading_number(self, project):
         assert "must begin with a letter" in str(exc.value)
 
 
-long_name_metrics_yml = """
-version: 2
-
-metrics:
-
-  - name: this_name_is_going_to_contain_more_than_250_characters_but_be_otherwise_acceptable_and_then_will_throw_an_error_which_I_expect_to_happen_and_repeat_this_name_is_going_to_contain_more_than_250_characters_but_be_otherwise_acceptable_and_then_will_throw_an_error_which_I_expect_to_happen
-    label: "Number of people"
-    description: Total count of people
-    model: "ref('people')"
-    calculation_method: count
-    expression: "*"
-    timestamp: created_at
-    time_grains: [day, week, month]
-    dimensions:
-      - favorite_color
-      - loves_dbt
-    meta:
-        my_meta: 'testing'
-
-"""
-
-
 class TestLongName:
     @pytest.fixture(scope="class")
     def models(self):
         return {
             "people_metrics.yml": long_name_metrics_yml,
-            "people.sql": models__people_sql,
+            "people.sql": models_people_sql,
         }
 
     def test_long_name(self, project):
@@ -368,96 +158,11 @@ def test_long_name(self, project):
         assert "cannot contain more than 250 characters" in str(exc.value)
 
 
-downstream_model_sql = """
--- this model will depend on these three metrics
-{% set some_metrics = [
-    metric('count_orders'),
-    metric('sum_order_revenue'),
-    metric('average_order_value')
-] %}
-
-/*
-{% if not execute %}
-
-    -- the only properties available to us at 'parse' time are:
-    --      'metric_name'
-    --      'package_name' (None if same package)
-
-    {% set metric_names = [] %}
-    {% for m in some_metrics %}
-        {% do metric_names.append(m.metric_name) %}
-    {% endfor %}
-
-    -- this config does nothing, but it lets us check these values below
-    {{ config(metric_names = metric_names) }}
-
-{% else %}
-
-    -- these are the properties available to us at 'execution' time
-
-    {% for m in some_metrics %}
-        name: {{ m.name }}
-        label: {{ m.label }}
-        calculation_method: {{ m.calculation_method }}
-        expression: {{ m.expression }}
-        timestamp: {{ m.timestamp }}
-        time_grains: {{ m.time_grains }}
-        dimensions: {{ m.dimensions }}
-        filters: {{ m.filters }}
-        window: {{ m.window }}
-    {% endfor %}
-
-{% endif %}
-
-select 1 as id
-"""
-
-invalid_derived_metric__contains_model_yml = """
-version: 2
-metrics:
-    - name: count_orders
-      label: Count orders
-      model: ref('mock_purchase_data')
-
-      calculation_method: count
-      expression: "*"
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-
-    - name: sum_order_revenue
-      label: Total order revenue
-      model: ref('mock_purchase_data')
-
-      calculation_method: sum
-      expression: "payment_total"
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-
-    - name: average_order_value
-      label: Average Order Value
-
-      calculation_method: derived
-      expression:  "{{metric('sum_order_revenue')}} / {{metric('count_orders')}} "
-      model: ref('mock_purchase_data')
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-"""
-
-
 class TestInvalidDerivedMetrics:
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "derived_metric.yml": invalid_derived_metric__contains_model_yml,
+            "derived_metric.yml": invalid_derived_metric_contains_model_yml,
             "downstream_model.sql": downstream_model_sql,
         }
 
@@ -466,46 +171,6 @@ def test_invalid_derived_metrics(self, project):
             run_dbt(["run"])
 
 
-derived_metric_yml = """
-version: 2
-metrics:
-    - name: count_orders
-      label: Count orders
-      model: ref('mock_purchase_data')
-
-      calculation_method: count
-      expression: "*"
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-
-    - name: sum_order_revenue
-      label: Total order revenue
-      model: ref('mock_purchase_data')
-
-      calculation_method: sum
-      expression: "payment_total"
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-
-    - name: average_order_value
-      label: Average Order Value
-
-      calculation_method: derived
-      expression:  "{{metric('sum_order_revenue')}} / {{metric('count_orders')}} "
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-"""
-
-
 class TestDerivedMetric:
     @pytest.fixture(scope="class")
     def models(self):
@@ -581,46 +246,6 @@ def test_derived_metric(
                 assert f"{property}: {expected_value}" in compiled_code
 
 
-derived_metric_old_attr_names_yml = """
-version: 2
-metrics:
-    - name: count_orders
-      label: Count orders
-      model: ref('mock_purchase_data')
-
-      type: count
-      sql: "*"
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-
-    - name: sum_order_revenue
-      label: Total order revenue
-      model: ref('mock_purchase_data')
-
-      type: sum
-      sql: "payment_total"
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-
-    - name: average_order_value
-      label: Average Order Value
-
-      type: expression
-      sql:  "{{metric('sum_order_revenue')}} / {{metric('count_orders')}} "
-      timestamp: purchased_at
-      time_grains: [day, week, month, quarter, year]
-
-      dimensions:
-        - payment_type
-"""
-
-
 class TestDerivedMetricOldAttrNames(TestDerivedMetric):
     @pytest.fixture(scope="class")
     def models(self):
diff --git a/tests/functional/sources/common_source_setup.py b/tests/functional/sources/common_source_setup.py
--- a/tests/functional/sources/common_source_setup.py
+++ b/tests/functional/sources/common_source_setup.py
@@ -4,16 +4,16 @@
 
 from dbt.tests.util import run_dbt
 from tests.functional.sources.fixtures import (
-    models__schema_yml,
-    models__view_model_sql,
-    models__ephemeral_model_sql,
-    models__descendant_model_sql,
-    models__multi_source_model_sql,
-    models__nonsource_descendant_sql,
-    seeds__source_csv,
-    seeds__other_table_csv,
-    seeds__expected_multi_source_csv,
-    seeds__other_source_table_csv,
+    models_schema_yml,
+    models_view_model_sql,
+    models_ephemeral_model_sql,
+    models_descendant_model_sql,
+    models_multi_source_model_sql,
+    models_nonsource_descendant_sql,
+    seeds_source_csv,
+    seeds_other_table_csv,
+    seeds_expected_multi_source_csv,
+    seeds_other_source_table_csv,
 )
 
 
@@ -29,21 +29,21 @@ def setEnvVars(self):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "schema.yml": models__schema_yml,
-            "view_model.sql": models__view_model_sql,
-            "ephemeral_model.sql": models__ephemeral_model_sql,
-            "descendant_model.sql": models__descendant_model_sql,
-            "multi_source_model.sql": models__multi_source_model_sql,
-            "nonsource_descendant.sql": models__nonsource_descendant_sql,
+            "schema.yml": models_schema_yml,
+            "view_model.sql": models_view_model_sql,
+            "ephemeral_model.sql": models_ephemeral_model_sql,
+            "descendant_model.sql": models_descendant_model_sql,
+            "multi_source_model.sql": models_multi_source_model_sql,
+            "nonsource_descendant.sql": models_nonsource_descendant_sql,
         }
 
     @pytest.fixture(scope="class")
     def seeds(self):
         return {
-            "source.csv": seeds__source_csv,
-            "other_table.csv": seeds__other_table_csv,
-            "expected_multi_source.csv": seeds__expected_multi_source_csv,
-            "other_source_table.csv": seeds__other_source_table_csv,
+            "source.csv": seeds_source_csv,
+            "other_table.csv": seeds_other_table_csv,
+            "expected_multi_source.csv": seeds_expected_multi_source_csv,
+            "other_source_table.csv": seeds_other_source_table_csv,
         }
 
     @pytest.fixture(scope="class")
diff --git a/tests/functional/sources/fixtures.py b/tests/functional/sources/fixtures.py
--- a/tests/functional/sources/fixtures.py
+++ b/tests/functional/sources/fixtures.py
@@ -1,4 +1,4 @@
-error_models__schema_yml = """version: 2
+error_models_schema_yml = """version: 2
 sources:
   - name: test_source
     loader: custom
@@ -12,10 +12,10 @@
         loaded_at_field: updated_at
 """
 
-error_models__model_sql = """select * from {{ source('test_source', 'test_table') }}
+error_models_model_sql = """select * from {{ source('test_source', 'test_table') }}
 """
 
-override_freshness_models__schema_yml = """version: 2
+override_freshness_models_schema_yml = """version: 2
 sources:
   - name: test_source
     loader: custom
@@ -59,7 +59,7 @@
         freshness: null # override: disable freshness for this table
 """
 
-models__schema_yml = """version: 2
+models_schema_yml = """version: 2
 models:
   - name: descendant_model
     columns:
@@ -142,31 +142,31 @@
       - name: table
 """
 
-models__view_model_sql = """{# See here: https://github.com/dbt-labs/dbt-core/pull/1729 #}
+models_view_model_sql = """{# See here: https://github.com/dbt-labs/dbt-core/pull/1729 #}
 
 select * from {{ ref('ephemeral_model') }}
 """
 
-models__ephemeral_model_sql = """{{ config(materialized='ephemeral') }}
+models_ephemeral_model_sql = """{{ config(materialized='ephemeral') }}
 
 select 1 as id
 """
 
-models__descendant_model_sql = """select * from {{ source('test_source', 'test_table') }}
+models_descendant_model_sql = """select * from {{ source('test_source', 'test_table') }}
 """
 
-models__multi_source_model_sql = """select * from {{ source('test_source', 'other_test_table')}}
+models_multi_source_model_sql = """select * from {{ source('test_source', 'other_test_table')}}
   join {{ source('other_source', 'test_table')}} using (id)
 """
 
-models__nonsource_descendant_sql = """select * from {{ schema }}.source
+models_nonsource_descendant_sql = """select * from {{ schema }}.source
 """
 
-models__newly_added_model_sql = """select 2 as id"""
+models_newly_added_model_sql = """select 2 as id"""
 
-models__newly_added_error_model_sql = """select error from fake_table"""
+models_newly_added_error_model_sql = """select error from fake_table"""
 
-malformed_models__schema_yml = """version: 2
+malformed_models_schema_yml = """version: 2
 sources:
   - name: test_source
     loader: custom
@@ -182,10 +182,10 @@
               - field: favorite_color
 """
 
-malformed_models__descendant_model_sql = """select * from {{ source('test_source', 'test_table') }}
+malformed_models_descendant_model_sql = """select * from {{ source('test_source', 'test_table') }}
 """
 
-filtered_models__schema_yml = """version: 2
+filtered_models_schema_yml = """version: 2
 sources:
   - name: test_source
     loader: custom
@@ -205,7 +205,7 @@
           filter: id > 101
 """
 
-macros__macro_sql = """{% macro override_me() -%}
+macros_macro_sql = """{% macro override_me() -%}
     {{ exceptions.raise_compiler_error('this is a bad macro') }}
 {%- endmacro %}
 
@@ -221,7 +221,7 @@
 {%- endmacro %}
 """
 
-seeds__source_csv = """favorite_color,id,first_name,email,ip_address,updated_at
+seeds_source_csv = """favorite_color,id,first_name,email,ip_address,updated_at
 blue,1,Larry,lking0@miitbeian.gov.cn,'69.135.206.194',2008-09-12 19:08:31
 blue,2,Larry,lperkins1@toplist.cz,'64.210.133.162',1978-05-09 04:15:14
 blue,3,Anna,amontgomery2@miitbeian.gov.cn,'168.104.64.114',2011-10-16 04:07:57
@@ -324,25 +324,25 @@
 green,100,Frank,fgreene2r@blogspot.com,'150.143.68.121',2010-06-12 23:55:39
 """
 
-seeds__other_table_csv = """id,first_name
+seeds_other_table_csv = """id,first_name
 1,Larry
 2,Curly
 3,Moe
 """
 
-seeds__expected_multi_source_csv = """id,first_name,color
+seeds_expected_multi_source_csv = """id,first_name,color
 1,Larry,blue
 2,Curly,red
 3,Moe,green
 """
 
-seeds__other_source_table_csv = """id,color
+seeds_other_source_table_csv = """id,color
 1,blue
 2,red
 3,green
 """
 
-malformed_schema_tests__schema_yml = """version: 2
+malformed_schema_tests_schema_yml = """version: 2
 sources:
   - name: test_source
     schema: "{{ var('test_run_schema') }}"
@@ -358,5 +358,84 @@
                   field: "{{ 'favorite' ~ 'color' }}"
 """
 
-malformed_schema_tests__model_sql = """select * from {{ source('test_source', 'test_table') }}
+malformed_schema_tests_model_sql = """select * from {{ source('test_source', 'test_table') }}
+"""
+
+basic_source_schema_yml = """version: 2
+
+sources:
+  - name: test_source
+    tables:
+      - name: test_table
+  - name: other_source
+    tables:
+      - name: test_table
+"""
+
+disabled_source_level_schema_yml = """version: 2
+
+sources:
+  - name: test_source
+    config:
+      enabled: False
+    tables:
+      - name: test_table
+      - name: disabled_test_table
+"""
+
+disabled_source_table_schema_yml = """version: 2
+
+sources:
+  - name: test_source
+    tables:
+      - name: test_table
+      - name: disabled_test_table
+        config:
+            enabled: False
+"""
+
+all_configs_everywhere_schema_yml = """version: 2
+
+sources:
+  - name: test_source
+    config:
+        enabled: False
+    tables:
+      - name: test_table
+        config:
+            enabled: True
+      - name: other_test_table
+"""
+
+all_configs_not_table_schema_yml = """version: 2
+
+sources:
+  - name: test_source
+    config:
+        enabled: True
+    tables:
+      - name: test_table
+      - name: other_test_table
+"""
+
+all_configs_project_source_schema_yml = """version: 2
+
+sources:
+  - name: test_source
+    tables:
+      - name: test_table
+        config:
+            enabled: True
+      - name: other_test_table
+"""
+
+invalid_config_source_schema_yml = """version: 2
+
+sources:
+  - name: test_source
+    tables:
+      - name: test_table
+        config:
+            enabled: True and False
+      - name: other_test_table
 """
diff --git a/tests/functional/sources/test_simple_source.py b/tests/functional/sources/test_simple_source.py
--- a/tests/functional/sources/test_simple_source.py
+++ b/tests/functional/sources/test_simple_source.py
@@ -13,11 +13,11 @@
     BaseSourcesTest,
 )
 from tests.functional.sources.fixtures import (
-    macros__macro_sql,
-    malformed_models__schema_yml,
-    malformed_models__descendant_model_sql,
-    malformed_schema_tests__schema_yml,
-    malformed_schema_tests__model_sql,
+    macros_macro_sql,
+    malformed_models_schema_yml,
+    malformed_models_descendant_model_sql,
+    malformed_schema_tests_schema_yml,
+    malformed_schema_tests_model_sql,
 )
 
 
@@ -33,7 +33,7 @@ def setUp(self, project):
 
     @pytest.fixture(scope="class")
     def macros(self):
-        return {"macro.sql": macros__macro_sql}
+        return {"macro.sql": macros_macro_sql}
 
     def _create_schemas(self, project):
         schema = self.alternative_schema(project.test_schema)
@@ -159,8 +159,8 @@ class TestMalformedSources(BaseSourcesTest):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "schema.yml": malformed_models__schema_yml,
-            "descendant_model.sql": malformed_models__descendant_model_sql,
+            "schema.yml": malformed_models_schema_yml,
+            "descendant_model.sql": malformed_models_descendant_model_sql,
         }
 
     def test_malformed_schema_will_break_run(self, project):
@@ -172,8 +172,8 @@ class TestRenderingInSourceTests(BaseSourcesTest):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "schema.yml": malformed_schema_tests__schema_yml,
-            "model.sql": malformed_schema_tests__model_sql,
+            "schema.yml": malformed_schema_tests_schema_yml,
+            "model.sql": malformed_schema_tests_model_sql,
         }
 
     def test_render_in_source_tests(self, project):
diff --git a/tests/functional/sources/test_source_configs.py b/tests/functional/sources/test_source_configs.py
--- a/tests/functional/sources/test_source_configs.py
+++ b/tests/functional/sources/test_source_configs.py
@@ -1,7 +1,18 @@
 import pytest
+from hologram import ValidationError
+
 from dbt.contracts.graph.model_config import SourceConfig
 
 from dbt.tests.util import run_dbt, update_config_file, get_manifest
+from tests.functional.sources.fixtures import (
+    basic_source_schema_yml,
+    disabled_source_level_schema_yml,
+    disabled_source_table_schema_yml,
+    all_configs_everywhere_schema_yml,
+    all_configs_not_table_schema_yml,
+    all_configs_project_source_schema_yml,
+    invalid_config_source_schema_yml,
+)
 
 
 class SourceConfigTests:
@@ -12,25 +23,13 @@ def setUp(self):
         )
 
 
-models__schema_yml = """version: 2
-
-sources:
-  - name: test_source
-    tables:
-      - name: test_table
-  - name: other_source
-    tables:
-      - name: test_table
-"""
-
-
 # Test enabled config in dbt_project.yml
 # expect pass, already implemented
 class TestSourceEnabledConfigProjectLevel(SourceConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "schema.yml": models__schema_yml,
+            "schema.yml": basic_source_schema_yml,
         }
 
     @pytest.fixture(scope="class")
@@ -69,24 +68,12 @@ def test_enabled_source_config_dbt_project(self, project):
         assert "source.test.other_source.test_table" in manifest.sources
 
 
-disabled_source_level__schema_yml = """version: 2
-
-sources:
-  - name: test_source
-    config:
-      enabled: False
-    tables:
-      - name: test_table
-      - name: disabled_test_table
-"""
-
-
 # Test enabled config at sources level in yml file
 class TestConfigYamlSourceLevel(SourceConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "schema.yml": disabled_source_level__schema_yml,
+            "schema.yml": disabled_source_level_schema_yml,
         }
 
     def test_source_config_yaml_source_level(self, project):
@@ -96,24 +83,12 @@ def test_source_config_yaml_source_level(self, project):
         assert "source.test.test_source.disabled_test_table" not in manifest.sources
 
 
-disabled_source_table__schema_yml = """version: 2
-
-sources:
-  - name: test_source
-    tables:
-      - name: test_table
-      - name: disabled_test_table
-        config:
-            enabled: False
-"""
-
-
 # Test enabled config at source table level in yaml file
 class TestConfigYamlSourceTable(SourceConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "schema.yml": disabled_source_table__schema_yml,
+            "schema.yml": disabled_source_table_schema_yml,
         }
 
     def test_source_config_yaml_source_table(self, project):
@@ -123,25 +98,11 @@ def test_source_config_yaml_source_table(self, project):
         assert "source.test.test_source.disabled_test_table" not in manifest.sources
 
 
-all_configs_everywhere__schema_yml = """version: 2
-
-sources:
-  - name: test_source
-    config:
-        enabled: False
-    tables:
-      - name: test_table
-        config:
-            enabled: True
-      - name: other_test_table
-"""
-
-
 # Test inheritence - set configs at project, source, and source-table level - expect source-table level to win
 class TestSourceConfigsInheritence1(SourceConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
-        return {"schema.yml": all_configs_everywhere__schema_yml}
+        return {"schema.yml": all_configs_everywhere_schema_yml}
 
     @pytest.fixture(scope="class")
     def project_config_update(self):
@@ -158,18 +119,6 @@ def test_source_all_configs_source_table(self, project):
         assert config_test_table == pytest.expected_config
 
 
-all_configs_not_table_schema_yml = """version: 2
-
-sources:
-  - name: test_source
-    config:
-        enabled: True
-    tables:
-      - name: test_table
-      - name: other_test_table
-"""
-
-
 # Test inheritence - set configs at project and source level - expect source level to win
 class TestSourceConfigsInheritence2(SourceConfigTests):
     @pytest.fixture(scope="class")
@@ -197,23 +146,11 @@ def test_source_two_configs_source_level(self, project):
         assert config_test_table == pytest.expected_config
 
 
-all_configs_project_source__schema_yml = """version: 2
-
-sources:
-  - name: test_source
-    tables:
-      - name: test_table
-        config:
-            enabled: True
-      - name: other_test_table
-"""
-
-
 # Test inheritence - set configs at project and source-table level - expect source-table level to win
 class TestSourceConfigsInheritence3(SourceConfigTests):
     @pytest.fixture(scope="class")
     def models(self):
-        return {"schema.yml": all_configs_project_source__schema_yml}
+        return {"schema.yml": all_configs_project_source_schema_yml}
 
     @pytest.fixture(scope="class")
     def project_config_update(self):
@@ -228,3 +165,18 @@ def test_source_two_configs_source_table(self, project):
 
         assert isinstance(config_test_table, SourceConfig)
         assert config_test_table == pytest.expected_config
+
+
+# Test invalid source configs
+class TestInvalidSourceConfig(SourceConfigTests):
+    @pytest.fixture(scope="class")
+    def models(self):
+        return {
+            "schema.yml": invalid_config_source_schema_yml,
+        }
+
+    def test_invalid_config_source(self, project):
+        with pytest.raises(ValidationError) as excinfo:
+            run_dbt(["parse"])
+        expected_msg = "'True and False' is not of type 'boolean'"
+        assert expected_msg in str(excinfo.value)
diff --git a/tests/functional/sources/test_source_fresher_state.py b/tests/functional/sources/test_source_fresher_state.py
--- a/tests/functional/sources/test_source_fresher_state.py
+++ b/tests/functional/sources/test_source_fresher_state.py
@@ -12,9 +12,9 @@
 from tests.functional.sources.common_source_setup import BaseSourcesTest
 
 from tests.functional.sources.fixtures import (
-    error_models__schema_yml,
-    models__newly_added_model_sql,
-    models__newly_added_error_model_sql,
+    error_models_schema_yml,
+    models_newly_added_model_sql,
+    models_newly_added_error_model_sql,
 )
 
 
@@ -273,7 +273,7 @@ def test_source_fresher_build_state_modified_pass(self, project, project_root):
         models_path = project_root.join("models/")
         assert os.path.exists(models_path)
         with open(f"{models_path}/newly_added_model.sql", "w") as fp:
-            fp.write(models__newly_added_model_sql)
+            fp.write(models_newly_added_model_sql)
 
         copy_to_previous_state()
         state_modified_results = self.run_dbt_with_vars(
@@ -305,7 +305,7 @@ class TestSourceFresherRuntimeError(SuccessfulSourceFreshnessTest):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "schema.yml": error_models__schema_yml,
+            "schema.yml": error_models_schema_yml,
         }
 
     def test_runtime_error_states(self, project):
@@ -656,7 +656,7 @@ def test_source_fresher_build_state_modified_pass(self, project, project_root):
         models_path = project_root.join("models/")
         assert os.path.exists(models_path)
         with open(f"{models_path}/newly_added_error_model.sql", "w") as fp:
-            fp.write(models__newly_added_error_model_sql)
+            fp.write(models_newly_added_error_model_sql)
 
         self.run_dbt_with_vars(project, ["run"], expect_pass=False)
 
diff --git a/tests/functional/sources/test_source_freshness.py b/tests/functional/sources/test_source_freshness.py
--- a/tests/functional/sources/test_source_freshness.py
+++ b/tests/functional/sources/test_source_freshness.py
@@ -6,10 +6,10 @@
 import dbt.version
 from tests.functional.sources.common_source_setup import BaseSourcesTest
 from tests.functional.sources.fixtures import (
-    error_models__schema_yml,
-    error_models__model_sql,
-    filtered_models__schema_yml,
-    override_freshness_models__schema_yml,
+    error_models_schema_yml,
+    error_models_model_sql,
+    filtered_models_schema_yml,
+    override_freshness_models_schema_yml,
 )
 from dbt.tests.util import AnyStringWith, AnyFloat
 
@@ -256,8 +256,8 @@ class TestSourceFreshnessErrors(SuccessfulSourceFreshnessTest):
     @pytest.fixture(scope="class")
     def models(self):
         return {
-            "schema.yml": error_models__schema_yml,
-            "model.sql": error_models__model_sql,
+            "schema.yml": error_models_schema_yml,
+            "model.sql": error_models_model_sql,
         }
 
     def test_source_freshness_error(self, project):
@@ -269,7 +269,7 @@ def test_source_freshness_error(self, project):
 class TestSourceFreshnessFilter(SuccessfulSourceFreshnessTest):
     @pytest.fixture(scope="class")
     def models(self):
-        return {"schema.yml": filtered_models__schema_yml}
+        return {"schema.yml": filtered_models_schema_yml}
 
     def test_source_freshness_all_records(self, project):
         # all records are filtered out
@@ -288,7 +288,7 @@ def test_source_freshness_all_records(self, project):
 class TestOverrideSourceFreshness(SuccessfulSourceFreshnessTest):
     @pytest.fixture(scope="class")
     def models(self):
-        return {"schema.yml": override_freshness_models__schema_yml}
+        return {"schema.yml": override_freshness_models_schema_yml}
 
     @staticmethod
     def get_result_from_unique_id(data, unique_id):