Repository: SFDO-Tooling/CumulusCI
Pull request #3469 (2022-12-14 00:10:55)

Add a better error message for scratch orgs that cannot be recreated because they were removed from YAML after being created in the past
From top to bottom:

1. Fix up some imports to "modern" syntax. Makes Pylance happy
2. Add a better error for the case where a scratch org config went away after a scratch org was created and before the system tries to re-create it based on its name.
3. Add some assertions that Pylance likes about missing orgs
4. Add a test for the new code.


Patch:
diff --git a/cumulusci/core/keychain/base_project_keychain.py b/cumulusci/core/keychain/base_project_keychain.py
index 10795875e..78f40a903 100644
--- a/cumulusci/core/keychain/base_project_keychain.py
+++ b/cumulusci/core/keychain/base_project_keychain.py
@@ -1,11 +1,8 @@
 import sarge
 
-from cumulusci.core.config import (
-    BaseConfig,
-    ConnectedAppOAuthConfig,
-    ScratchOrgConfig,
-    ServiceConfig,
-)
+from cumulusci.core.config import ConnectedAppOAuthConfig, ServiceConfig
+from cumulusci.core.config.base_config import BaseConfig
+from cumulusci.core.config.scratch_org_config import ScratchOrgConfig
 from cumulusci.core.exceptions import (
     CumulusCIException,
     CumulusCIUsageError,
@@ -60,6 +57,8 @@ class BaseProjectKeychain(BaseConfig):
     def create_scratch_org(self, org_name, config_name, days=None, set_password=True):
         """Adds/Updates a scratch org config to the keychain from a named config"""
         scratch_config = self.project_config.lookup(f"orgs__scratch__{config_name}")
+        if scratch_config is None:
+            raise OrgNotFound(f"No such org configured: `{config_name}`")
         if days is not None:
             # Allow override of scratch config's default days
             scratch_config["days"] = days
@@ -86,6 +85,7 @@ class BaseProjectKeychain(BaseConfig):
     def set_default_org(self, name):
         """set the default org for tasks and flows by name"""
         org = self.get_org(name)
+        assert org is not None
         self.unset_default_org()
         org.config["default"] = True
         org.save()
@@ -100,6 +100,7 @@ class BaseProjectKeychain(BaseConfig):
         """unset the default orgs for tasks"""
         for org in self.list_orgs():
             org_config = self.get_org(org)
+            assert org_config is not None
             if org_config.default:
                 del org_config.config["default"]
                 org_config.save()
@@ -114,6 +115,7 @@ class BaseProjectKeychain(BaseConfig):
         """retrieve the name and configuration of the default org"""
         for org in self.list_orgs():
             org_config = self.get_org(org)
+            assert org_config is not None
             if org_config.default:
                 return org, org_config
         return None, None
@@ -121,6 +123,7 @@ class BaseProjectKeychain(BaseConfig):
     def get_org(self, name: str):
         """retrieve an org configuration by name key"""
         org = self._get_org(name)
+        assert org
         if org.keychain:
             assert org.keychain is self
         else:


Test patch:
diff --git a/cumulusci/core/keychain/tests/test_base_project_keychain.py b/cumulusci/core/keychain/tests/test_base_project_keychain.py
index 6f18f3f17..06e9757fd 100644
--- a/cumulusci/core/keychain/tests/test_base_project_keychain.py
+++ b/cumulusci/core/keychain/tests/test_base_project_keychain.py
@@ -290,3 +290,9 @@ class TestBaseProjectKeychain:
         keychain.remove_org("test")
         assert "test" not in keychain.orgs
         keychain.cleanup_org_cache_dirs.assert_called_once()
+
+    def test_org_definition__missing(self, project_config, key):
+        """What if a scratch org was created with a YAML definition which was deleted more recently?"""
+        keychain = BaseProjectKeychain(project_config, key)
+        with pytest.raises(OrgNotFound, match="No such org"):
+            keychain.create_scratch_org("no_such_org", "no_such_org")