From 8b7e8750387e5f61ca5694655d2c680ca2d6c0e2 Mon Sep 17 00:00:00 2001
From: mishaschwartz <4380924+mishaschwartz@users.noreply.github.com>
Date: Tue, 7 Jan 2025 09:03:43 -0500
Subject: [PATCH] add changes and formatting updates

---
 CHANGES.md                              |  27 ++++-
 tests/conftest.py                       |   6 +-
 tests/integration/conftest.py           |  30 ++---
 tests/integration/test_magpie.py        |   5 +-
 tests/unit/test_cli.py                  |  20 +---
 tests/unit/test_deployment.py           |  46 ++------
 tests/unit/test_read_configs_include.py | 142 ++++++------------------
 7 files changed, 89 insertions(+), 187 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 1cbceeac..9b1622b3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -15,7 +15,32 @@
 [Unreleased](https://github.com/bird-house/birdhouse-deploy/tree/master) (latest)
 ------------------------------------------------------------------------------------------------------------------
 
-[//]: # (list changes here, using '-' for each new entry, remove this when items are added)
+## Changes
+
+- Add integration test framework
+
+  This update adds a framework for testing the deployed stack using pytest. This will allow developers to check
+  that their changes are consistent with the existing stack and to add additionally tests when new functionality
+  is introduced. 
+
+  Changes to implement this include:
+
+  - existing unit tests are moved to the `tests/unit/` directory
+  - new integration tests are written in the `tests/integration/` directory. More tests will be added in the
+    future!
+  - `conftest.py` scripts updated to bring the stack up/down in a consistent way for the integration tests.
+  - unit tests updated to accomodate new testing infrastructure as needed.
+  - unit tests updated to test logging outputs better
+  - `birdhouse` interface script updated to support testing infrastructure (this should not change anything for
+    other end-users).
+  - additional documentation added to `birdhouse` interface to improve user experience.
+  - docker healthchecks added to more components so that the readiness of the stack can be determined with or
+    without the use of the `canarie-api` component.
+
+  Next steps:
+
+  - add more integration tests as needed
+  - add a framework for testing migrating the stack from one version to another
 
 [2.7.1](https://github.com/bird-house/birdhouse-deploy/tree/2.7.1) (2024-12-20)
 ------------------------------------------------------------------------------------------------------------------
diff --git a/tests/conftest.py b/tests/conftest.py
index d55a4c27..6adfdb65 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,6 +2,7 @@
 import pathlib
 import pytest
 
+
 # These are used for integration tests only but are defined here so that tests run from the top level tests/
 # directory will execute without error.
 def pytest_addoption(parser):
@@ -27,6 +28,7 @@ def pytest_addoption(parser):
         help="Number of seconds to wait for the stack to be healthy after it starts up",
     )
 
+
 @pytest.fixture(scope="module")
 def root_dir(request):
     # implement this for every testing subfolder
@@ -35,6 +37,4 @@ def root_dir(request):
 
 @pytest.fixture(scope="module")
 def local_env_file(root_dir):
-    yield pathlib.Path(
-        os.getenv("TEST_BIRDHOUSE_LOCAL_ENV", root_dir / "tests" / "env.local.test")
-    )
+    yield pathlib.Path(os.getenv("TEST_BIRDHOUSE_LOCAL_ENV", root_dir / "tests" / "env.local.test"))
diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py
index 7ca140d1..52454f59 100644
--- a/tests/integration/conftest.py
+++ b/tests/integration/conftest.py
@@ -37,9 +37,7 @@ def check_stack_not_running(request):
     project_name = os.getenv("COMPOSE_PROJECT_NAME", "birdhouse")
     lines = proc.stdout.splitlines()
     if test_project_name in lines or project_name in lines:
-        pytest.fail(
-            "Birdhouse is currently running. Please stop the software before running tests."
-        )
+        pytest.fail("Birdhouse is currently running. Please stop the software before running tests.")
 
 
 @pytest.fixture(scope="session")
@@ -70,9 +68,7 @@ def load_stack_env(cli_path, local_env_file, stack_info):
     )
     if proc.returncode:
         pytest.fail(f"Unable to get environment variables. Error:\n{proc.stderr}")
-    env_vars = dict(
-        line.split("=", 1) for line in proc.stdout.split("\x00") if "=" in line
-    )
+    env_vars = dict(line.split("=", 1) for line in proc.stdout.split("\x00") if "=" in line)
     print(env_vars["MAGPIE_ADMIN_USERNAME"])
     stack_info["env_vars"] = env_vars
 
@@ -84,15 +80,11 @@ def stack_env(stack_info):
 
 @pytest.fixture(scope="module")
 def birdhouse_url(stack_env):
-    return (
-        f"{stack_env['BIRDHOUSE_PROXY_SCHEME']}://{stack_env['BIRDHOUSE_FQDN_PUBLIC']}"
-    )
+    return f"{stack_env['BIRDHOUSE_PROXY_SCHEME']}://{stack_env['BIRDHOUSE_FQDN_PUBLIC']}"
 
 
 @pytest.fixture(scope="module", autouse=True)
-def start_stack(
-    request, cli_path, local_env_file, tmp_data_persist_root, stack_info, pytestconfig
-):
+def start_stack(request, cli_path, local_env_file, tmp_data_persist_root, stack_info, pytestconfig):
     """
     Starts the birdhouse stack at the beginning of the test session.
 
@@ -139,8 +131,7 @@ def start_stack(
     while start + timeout > time.time():
         proc = subprocess.run(
             'docker inspect --format \'{{if .State.Health}}{"health": {{json .State.Health.Status}}, '
-            '"name": {{json .Name}}}{{end}}\' '
-            + containers_proc.stdout.replace("\n", " "),
+            '"name": {{json .Name}}}{{end}}\' ' + containers_proc.stdout.replace("\n", " "),
             shell=True,
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
@@ -153,13 +144,9 @@ def start_stack(
             if status.strip():
                 status = json.loads(status)
                 if status["health"] != "healthy":
-                    health_stats.append(
-                        f"name: '{status['name'].strip().strip('/')}', status: '{status['health']}'"
-                    )
+                    health_stats.append(f"name: '{status['name'].strip().strip('/')}', status: '{status['health']}'")
         if any(health_stats):
-            msg = "Waiting on the following containers to be healthy:\n" + "\n".join(
-                health_stats
-            )
+            msg = "Waiting on the following containers to be healthy:\n" + "\n".join(health_stats)
             print(msg, file=sys.stderr)
         else:
             break
@@ -179,8 +166,7 @@ def stop_stack(request, stack_info, pytestconfig):
     flags = "-s" if pytestconfig.option.capture == "no" else ""
     if stack_info["started"] and not request.config.getoption("--no-stop-stack", None):
         proc = subprocess.run(
-            f"{stack_info['cli_path']} {flags} -e '{stack_info['local_env_file']}' "
-            "compose down -v --remove-orphans",
+            f"{stack_info['cli_path']} {flags} -e '{stack_info['local_env_file']}' " "compose down -v --remove-orphans",
             shell=True,
             stderr=subprocess.PIPE,
             universal_newlines=True,
diff --git a/tests/integration/test_magpie.py b/tests/integration/test_magpie.py
index 22317814..c8aebfc7 100644
--- a/tests/integration/test_magpie.py
+++ b/tests/integration/test_magpie.py
@@ -22,7 +22,4 @@ def test_admin_can_log_in(magpie_url, stack_env):
         },
     )
     response.raise_for_status()
-    assert any(
-        cookie.domain == stack_env["BIRDHOUSE_FQDN_PUBLIC"]
-        for cookie in response.cookies
-    )
+    assert any(cookie.domain == stack_env["BIRDHOUSE_FQDN_PUBLIC"] for cookie in response.cookies)
diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py
index 18f32a83..e007c486 100644
--- a/tests/unit/test_cli.py
+++ b/tests/unit/test_cli.py
@@ -124,9 +124,7 @@ def test_compose_backwards_compatible(cli_path, run, printenv_script, flag):
 
 
 @pytest.mark.parametrize("flag", ["--env-file ", "-e ", "--env-file=", "-e="])
-def test_compose_set_env_file(
-    cli_path, run, printenv_script, local_env_file, tmp_path, flag
-):
+def test_compose_set_env_file(cli_path, run, printenv_script, local_env_file, tmp_path, flag):
     other_local_env_file = tmp_path / "env.local.other"
     with open(local_env_file) as f:
         other_local_env_file.write_text(f.read())
@@ -176,10 +174,7 @@ def test_configs_set_env_file(cli_path, run, local_env_file, tmp_path, flag):
         other_local_env_file.write_text(f.read())
     proc = run(f"{cli_path} {flag}{other_local_env_file} configs -p")
     assert f"BIRDHOUSE_LOCAL_ENV='{other_local_env_file}'" in proc.stdout
-    assert (
-        f"BIRDHOUSE_LOCAL_ENV='{local_env_file}'"
-        in proc.stdout.split(str(other_local_env_file))[-1]
-    )
+    assert f"BIRDHOUSE_LOCAL_ENV='{local_env_file}'" in proc.stdout.split(str(other_local_env_file))[-1]
 
 
 @pytest.mark.parametrize("flag", ["-s", "--log-stdout"])
@@ -245,18 +240,14 @@ def test_log_level(cli_path, run, logging_script, level):
 @pytest.mark.parametrize("level", LOG_LEVELS)
 def test_log_override_stdout(cli_path, run, logging_script, level):
     proc = run(f"{cli_path} -L DEBUG -s {level} compose", compose=logging_script)
-    check_log_output(
-        [level_ for level_ in LOG_CHECK_LEVELS if level_ != level], proc.stderr
-    )
+    check_log_output([level_ for level_ in LOG_CHECK_LEVELS if level_ != level], proc.stderr)
     check_log_output([level], proc.stdout)
 
 
 @pytest.mark.parametrize("level", LOG_LEVELS)
 def test_log_override_quiet(cli_path, run, logging_script, level):
     proc = run(f"{cli_path} -L DEBUG -q {level} compose", compose=logging_script)
-    check_log_output(
-        [level_ for level_ in LOG_CHECK_LEVELS if level_ != level], proc.stderr
-    )
+    check_log_output([level_ for level_ in LOG_CHECK_LEVELS if level_ != level], proc.stderr)
     check_log_output([], proc.stdout)
 
 
@@ -275,8 +266,7 @@ def test_log_override_file(cli_path, run, logging_script, tmp_path, level):
 def test_configs_log_override_multiple(cli_path, run, logging_script, tmp_path):
     log_file = tmp_path / "test.log"
     proc = run(
-        f"{cli_path} -L DEBUG -l DEBUG {log_file} -s INFO "
-        f"-q WARN -l ERROR {log_file} -q ERROR compose",
+        f"{cli_path} -L DEBUG -l DEBUG {log_file} -s INFO " f"-q WARN -l ERROR {log_file} -q ERROR compose",
         compose=logging_script,
     )
     check_log_output(["DEBUG"], proc.stderr)
diff --git a/tests/unit/test_deployment.py b/tests/unit/test_deployment.py
index 5ceff8b6..e13f139c 100644
--- a/tests/unit/test_deployment.py
+++ b/tests/unit/test_deployment.py
@@ -12,9 +12,7 @@
 TEMPLATE_SUBSTITUTIONS = {
     "BIRDHOUSE_FQDN_PUBLIC": os.environ.get("BIRDHOUSE_FQDN_PUBLIC", "example.com"),
     "WEAVER_MANAGER_NAME": os.environ.get("WEAVER_MANAGER_NAME", "weaver"),
-    "TWITCHER_PROTECTED_PATH": os.environ.get(
-        "TWITCHER_PROTECTED_PATH", "/twitcher/ows/proxy"
-    ),
+    "TWITCHER_PROTECTED_PATH": os.environ.get("TWITCHER_PROTECTED_PATH", "/twitcher/ows/proxy"),
     "BIRDHOUSE_PROXY_SCHEME": os.environ.get("BIRDHOUSE_PROXY_SCHEME", "http"),
 }
 
@@ -22,11 +20,7 @@
 @pytest.fixture(scope="module")
 def component_paths(root_dir):
     # type: (str) -> List[str]
-    yield [
-        path
-        for loc in COMPONENT_LOCATIONS
-        for path in glob.glob(os.path.join(root_dir, "birdhouse", loc, "*"))
-    ]
+    yield [path for loc in COMPONENT_LOCATIONS for path in glob.glob(os.path.join(root_dir, "birdhouse", loc, "*"))]
 
 
 @pytest.fixture(scope="module")
@@ -51,9 +45,7 @@ def template_substitutions(component_paths):
     return templates
 
 
-@pytest.fixture(
-    scope="module", params=[pytest.lazy_fixture("component_service_configs")]
-)
+@pytest.fixture(scope="module", params=[pytest.lazy_fixture("component_service_configs")])
 def resolved_services_config_schema(request):
     """
     For each of the services provided by ``component_paths`` fixture, obtain the referenced ``$schema``.
@@ -61,9 +53,7 @@ def resolved_services_config_schema(request):
     If variable ``DACCS_NODE_REGISTRY_BRANCH`` is defined, the referenced ``$schema`` is ignored in favor of it.
     """
     service_config_paths = request.param
-    assert (
-        service_config_paths
-    ), "Invalid service configuration. No service config found."
+    assert service_config_paths, "Invalid service configuration. No service config found."
 
     # test override
     branch = os.environ.get("DACCS_NODE_REGISTRY_BRANCH", None)
@@ -95,23 +85,17 @@ def load_templated_service_config(service_config_path, template_variables):
     Each service configuration file is expected to be an array of 'service' to allow multiple entries.
     """
     with open(service_config_path) as service_config_file:
-        service_config_json = Template(service_config_file.read()).safe_substitute(
-            template_variables
-        )
+        service_config_json = Template(service_config_file.read()).safe_substitute(template_variables)
         service_configs = json.loads(service_config_json)
     return service_configs
 
 
 class TestDockerCompose:
-    def test_service_config_name_same_as_dirname(
-        self, component_service_configs, template_substitutions
-    ):
+    def test_service_config_name_same_as_dirname(self, component_service_configs, template_substitutions):
         invalid_names = []
 
         for service_config_path in component_service_configs:
-            service_configs = load_templated_service_config(
-                service_config_path, template_substitutions
-            )
+            service_configs = load_templated_service_config(service_config_path, template_substitutions)
             invalid_config_names = []
             for service_config in service_configs:
                 config_name = service_config.get("name")
@@ -125,24 +109,16 @@ def test_service_config_name_same_as_dirname(
         assert not invalid_names, "service names in service-config.json.template should match the directory name"
 
     @pytest.mark.online
-    def test_service_config_valid(
-        self, resolved_services_config_schema, template_substitutions
-    ):
+    def test_service_config_valid(self, resolved_services_config_schema, template_substitutions):
         invalid_schemas = []
         for (
             service_config_schema,
             service_config_path,
         ) in resolved_services_config_schema:
-            service_configs = load_templated_service_config(
-                service_config_path, template_substitutions
-            )
+            service_configs = load_templated_service_config(service_config_path, template_substitutions)
             for service_config in service_configs:
                 try:
-                    jsonschema.validate(
-                        instance=service_config, schema=service_config_schema
-                    )
+                    jsonschema.validate(instance=service_config, schema=service_config_schema)
                 except jsonschema.exceptions.ValidationError as e:
-                    invalid_schemas.append(
-                        f"{service_config_path} contains invalid service configuration: {e}"
-                    )
+                    invalid_schemas.append(f"{service_config_path} contains invalid service configuration: {e}")
         assert not invalid_schemas, "\n".join(invalid_schemas)
diff --git a/tests/unit/test_read_configs_include.py b/tests/unit/test_read_configs_include.py
index 63c299ad..24a5b0e1 100644
--- a/tests/unit/test_read_configs_include.py
+++ b/tests/unit/test_read_configs_include.py
@@ -41,9 +41,7 @@ def set_local_env(env_file: io.FileIO, content: Union[str, dict]) -> None:
         content = {**DEFAULT_BIRDHOUSE_ENV, **content}
         env_file.write("\n".join(f"export {k}={v}" for k, v in content.items()))
     else:
-        default_content = "\n".join(
-            [f"{k}={v}" for k, v in DEFAULT_BIRDHOUSE_ENV.items()]
-        )
+        default_content = "\n".join([f"{k}={v}" for k, v in DEFAULT_BIRDHOUSE_ENV.items()])
         env_file.write(f"{default_content}\n{content}")
 
 
@@ -172,9 +170,7 @@ def test_all_conf_dirs_set(self, read_config_include_file, exit_on_error) -> Non
         assert get_command_stdout(proc).strip()
 
     @pytest.mark.usefixtures("run_in_compose_dir")
-    def test_all_conf_dirs_default_order(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    def test_all_conf_dirs_default_order(self, read_config_include_file, exit_on_error) -> None:
         """Test that the expected order that default.env files are loaded is correct"""
         proc = self.run_func(
             read_config_include_file,
@@ -183,18 +179,11 @@ def test_all_conf_dirs_default_order(
             exit_on_error=exit_on_error,
         )
         print(proc.stdout)  # useful for debugging when assert fail
-        assert (
-            split_and_strip(get_command_stdout(proc))
-            == self.default_all_conf_order_with_dependencies
-        )
+        assert split_and_strip(get_command_stdout(proc)) == self.default_all_conf_order_with_dependencies
 
-    def test_all_conf_dirs_extra_last(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    def test_all_conf_dirs_extra_last(self, read_config_include_file, exit_on_error) -> None:
         """Test that any extra components are loaded last"""
-        extra = {
-            "BIRDHOUSE_EXTRA_CONF_DIRS": '"./components/finch\n./components/weaver"'
-        }
+        extra = {"BIRDHOUSE_EXTRA_CONF_DIRS": '"./components/finch\n./components/weaver"'}
         proc = self.run_func(
             read_config_include_file,
             extra,
@@ -207,9 +196,7 @@ def test_all_conf_dirs_extra_last(
         ]
 
     @pytest.mark.usefixtures("run_in_compose_dir")
-    def test_dependencies_loaded_first(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    def test_dependencies_loaded_first(self, read_config_include_file, exit_on_error) -> None:
         """Test that dependencies are loaded first"""
         extra = {"BIRDHOUSE_EXTRA_CONF_DIRS": '"./optional-components/test-weaver"'}
         proc = self.run_func(
@@ -224,9 +211,7 @@ def test_dependencies_loaded_first(
             "./optional-components/test-weaver",
         ]
 
-    def test_non_project_components_included(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    def test_non_project_components_included(self, read_config_include_file, exit_on_error) -> None:
         """Test that extra components can be included"""
         extra = {"BIRDHOUSE_EXTRA_CONF_DIRS": '"./blah/other-random-component"'}
         proc = self.run_func(
@@ -235,15 +220,10 @@ def test_non_project_components_included(
             'echo "$ALL_CONF_DIRS"',
             exit_on_error=exit_on_error,
         )
-        assert (
-            split_and_strip(get_command_stdout(proc))[-1]
-            == "./blah/other-random-component"
-        )
+        assert split_and_strip(get_command_stdout(proc))[-1] == "./blah/other-random-component"
 
     @pytest.mark.usefixtures("run_in_compose_dir")
-    def test_delayed_eval_default_value(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    def test_delayed_eval_default_value(self, read_config_include_file, exit_on_error) -> None:
         """Test delayed eval when value not set in env.local"""
         extra = {
             "BIRDHOUSE_FQDN": '"fqdn.example.com"',
@@ -263,9 +243,7 @@ def test_delayed_eval_default_value(
         )
 
     @pytest.mark.usefixtures("run_in_compose_dir")
-    def test_delayed_eval_custom_value(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    def test_delayed_eval_custom_value(self, read_config_include_file, exit_on_error) -> None:
         """Test delayed eval when value is set in env.local"""
         extra = {
             "BIRDHOUSE_FQDN": '"fqdn.example.com"',
@@ -287,9 +265,7 @@ def test_delayed_eval_custom_value(
             == "public.example.com - /my-data-root/jupyterhub_user_data - /my-geoserver-data"
         )
 
-    def test_delayed_eval_quoting(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    def test_delayed_eval_quoting(self, read_config_include_file, exit_on_error) -> None:
         """Test that the delayed evaluation functions resolve quotation marks and braces properly"""
         extra = {
             "EXTRA_TEST_VAR": "\"{'123'}\"",
@@ -359,20 +335,10 @@ class TestBackwardsCompatible(_ReadConfigsFromEnvFile):
         SERVER_LICENSE_URL=BIRDHOUSE_LICENSE_URL
     """
 
-    old_vars = {
-        line.strip().split("=")[0]: "old"
-        for line in all_overrides.splitlines()
-        if line.strip()
-    }
-    new_vars = {
-        line.strip().split("=")[1]: "new"
-        for line in all_overrides.splitlines()
-        if line.strip()
-    }
-
-    def test_allowed_simple_substitution(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    old_vars = {line.strip().split("=")[0]: "old" for line in all_overrides.splitlines() if line.strip()}
+    new_vars = {line.strip().split("=")[1]: "new" for line in all_overrides.splitlines() if line.strip()}
+
+    def test_allowed_simple_substitution(self, read_config_include_file, exit_on_error) -> None:
         """
         Test that a deprecated variable can be used to set the new version if backwards compatible
         variables are allowed.
@@ -389,9 +355,7 @@ def test_allowed_simple_substitution(
         )
         assert split_and_strip(get_command_stdout(proc))[-1] == "fqdn.example.com"
 
-    def test_not_allowed_simple_substitution(
-        self, read_config_include_file, exit_on_error
-    ):
+    def test_not_allowed_simple_substitution(self, read_config_include_file, exit_on_error):
         """
         Test that a deprecated variable cannot be used to set the new version if backwards compatible
         variables are not allowed.
@@ -408,9 +372,7 @@ def test_not_allowed_simple_substitution(
         )
         assert not split_and_strip(get_command_stdout(proc))
 
-    def test_allowed_simple_override(
-        self, read_config_include_file, exit_on_error
-    ) -> None:
+    def test_allowed_simple_override(self, read_config_include_file, exit_on_error) -> None:
         """
         Test that a deprecated variable can be used to override the new version if backwards compatible
         variables are allowed.
@@ -451,9 +413,7 @@ def test_allowed_substitution_all(self, read_config_include_file, exit_on_error)
         Test that all deprecated variables can be used to set the new versions if backwards compatible
         variables are allowed.
         """
-        command_suffix = (
-            f'echo "{ENV_SPLIT_STR_ALT.join(f"{k}=${k}" for k in self.new_vars)}"'
-        )
+        command_suffix = f'echo "{ENV_SPLIT_STR_ALT.join(f"{k}=${k}" for k in self.new_vars)}"'
         proc = self.run_func(
             read_config_include_file,
             {"BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED": "True", **self.old_vars},
@@ -463,26 +423,19 @@ def test_allowed_substitution_all(self, read_config_include_file, exit_on_error)
         expected = set()
         for k in self.new_vars:
             if k == "BIRDHOUSE_EXTRA_CONF_DIRS":
-                expected.add(
-                    f"{k}=old ./optional-components/backwards-compatible-overrides"
-                )
+                expected.add(f"{k}=old ./optional-components/backwards-compatible-overrides")
             else:
                 expected.add(f"{k}=old")
         assert {
-            re.sub(r"[\s\n]+", " ", val.strip())
-            for val in get_command_stdout(proc).split(ENV_SPLIT_STR_ALT)
+            re.sub(r"[\s\n]+", " ", val.strip()) for val in get_command_stdout(proc).split(ENV_SPLIT_STR_ALT)
         } == expected
 
-    def test_not_allowed_substitution_all(
-        self, read_config_include_file, exit_on_error
-    ):
+    def test_not_allowed_substitution_all(self, read_config_include_file, exit_on_error):
         """
         Test that all deprecated variables are not used to set the new versions if backwards compatible
         variables are not allowed.
         """
-        command_suffix = (
-            f'echo "{ENV_SPLIT_STR_ALT.join(f"{k}=${k}" for k in self.new_vars)}"'
-        )
+        command_suffix = f'echo "{ENV_SPLIT_STR_ALT.join(f"{k}=${k}" for k in self.new_vars)}"'
         proc = self.run_func(
             read_config_include_file,
             {"BIRDHOUSE_BACKWARD_COMPATIBLE_ALLOWED": "False", **self.old_vars},
@@ -492,10 +445,7 @@ def test_not_allowed_substitution_all(
         expected = set()
         for k in self.new_vars:
             expected.add(f"{k}=new")
-        actual = [
-            re.sub(r"[\s\n]+", " ", val.strip())
-            for val in get_command_stdout(proc).split(ENV_SPLIT_STR_ALT)
-        ]
+        actual = [re.sub(r"[\s\n]+", " ", val.strip()) for val in get_command_stdout(proc).split(ENV_SPLIT_STR_ALT)]
         assert all(val != "new" for val in actual)
 
     def test_allowed_override_all(self, read_config_include_file, exit_on_error):
@@ -503,9 +453,7 @@ def test_allowed_override_all(self, read_config_include_file, exit_on_error):
         Test that all deprecated variables can be used to override the new versions if backwards compatible
         variables are allowed.
         """
-        command_suffix = (
-            f'echo "{ENV_SPLIT_STR_ALT.join(f"{k}=${k}" for k in self.new_vars)}"'
-        )
+        command_suffix = f'echo "{ENV_SPLIT_STR_ALT.join(f"{k}=${k}" for k in self.new_vars)}"'
         proc = self.run_func(
             read_config_include_file,
             {
@@ -519,14 +467,11 @@ def test_allowed_override_all(self, read_config_include_file, exit_on_error):
         expected = set()
         for k in self.new_vars:
             if k == "BIRDHOUSE_EXTRA_CONF_DIRS":
-                expected.add(
-                    f"{k}=old ./optional-components/backwards-compatible-overrides"
-                )
+                expected.add(f"{k}=old ./optional-components/backwards-compatible-overrides")
             else:
                 expected.add(f"{k}=old")
         assert {
-            re.sub(r"[\s\n]+", " ", val.strip())
-            for val in get_command_stdout(proc).split(ENV_SPLIT_STR_ALT)
+            re.sub(r"[\s\n]+", " ", val.strip()) for val in get_command_stdout(proc).split(ENV_SPLIT_STR_ALT)
         } == expected
 
     def test_not_allowed_override_all(self, read_config_include_file, exit_on_error):
@@ -534,9 +479,7 @@ def test_not_allowed_override_all(self, read_config_include_file, exit_on_error)
         Test that all deprecated variables are not used to override the new versions if backwards compatible
         variables are not allowed.
         """
-        command_suffix = (
-            f'echo "{ENV_SPLIT_STR_ALT.join(f"{k}=${k}" for k in self.new_vars)}"'
-        )
+        command_suffix = f'echo "{ENV_SPLIT_STR_ALT.join(f"{k}=${k}" for k in self.new_vars)}"'
         proc = self.run_func(
             read_config_include_file,
             {
@@ -547,14 +490,11 @@ def test_not_allowed_override_all(self, read_config_include_file, exit_on_error)
             command_suffix,
             exit_on_error=exit_on_error,
         )
-        assert {
-            re.sub(r"[\s\n]+", " ", val.strip())
-            for val in get_command_stdout(proc).split(ENV_SPLIT_STR_ALT)
-        } == {f"{k}=new" for k in self.new_vars}
+        assert {re.sub(r"[\s\n]+", " ", val.strip()) for val in get_command_stdout(proc).split(ENV_SPLIT_STR_ALT)} == {
+            f"{k}=new" for k in self.new_vars
+        }
 
-    def test_allowed_set_old_variables_when_unset(
-        self, read_config_include_file, exit_on_error
-    ):
+    def test_allowed_set_old_variables_when_unset(self, read_config_include_file, exit_on_error):
         """
         Test that new variables can be used to set deprecated variables when the deprecated variable is unset if
         backwards compatible variables are allowed.
@@ -571,9 +511,7 @@ def test_allowed_set_old_variables_when_unset(
         )
         assert split_and_strip(get_command_stdout(proc))[-1] == "birdhouse.example.com"
 
-    def test_not_allowed_set_old_variables_when_unset(
-        self, read_config_include_file, exit_on_error
-    ):
+    def test_not_allowed_set_old_variables_when_unset(self, read_config_include_file, exit_on_error):
         """
         Test that new variables cannot be used to set deprecated variables when the deprecated variable is unset if
         backwards compatible variables are not allowed.
@@ -590,9 +528,7 @@ def test_not_allowed_set_old_variables_when_unset(
         )
         assert not split_and_strip(get_command_stdout(proc))
 
-    def test_allowed_no_override_old_variables_when_set(
-        self, read_config_include_file, exit_on_error
-    ):
+    def test_allowed_no_override_old_variables_when_set(self, read_config_include_file, exit_on_error):
         """
         Test that new variables cannot be used to override deprecated variables when the deprecated variable is set if
         backwards compatible variables are allowed.
@@ -732,17 +668,9 @@ def test_compose_overrides(self, read_config_include_file, exit_on_error):
     def test_default_all_conf_dirs(self, read_config_include_file, exit_on_error):
         proc = self.run_func(
             read_config_include_file,
-            {
-                "ALL_CONF_DIRS": " ".join(
-                    TestReadConfigs.default_all_conf_order
-                    + TestReadConfigs.extra_conf_order
-                )
-            },
+            {"ALL_CONF_DIRS": " ".join(TestReadConfigs.default_all_conf_order + TestReadConfigs.extra_conf_order)},
             'echo "$COMPOSE_CONF_LIST"',
             exit_on_error=exit_on_error,
         )
         print(proc.stdout)  # useful for debugging when assert fail
-        assert (
-            split_and_strip(get_command_stdout(proc), split_on="-f")
-            == self.default_conf_list_order
-        )
+        assert split_and_strip(get_command_stdout(proc), split_on="-f") == self.default_conf_list_order