Repository: SFDO-Tooling/CumulusCI
Pull request #3102 (2022-03-01 20:04:34)

Code cleanups: remove org_config in MP and sf_version
1. Don't pass org_config to mapping_parser functions. Pass salesforce_client instead. The only reason they need an org_config is for the salesforce_client, and passing in an existing one is cleaner, more performant and easier to mock.

2. Start the process of centralizing the "preferred" Salesforce version for test cases in a single place.

Nothing relevant to release notes.

davisagli: There's a lint error. Maybe a version mismatch between black in requirements.txt and black in .pre-commit.yaml?

Patch:
diff --git a/cumulusci/tasks/bulkdata/extract.py b/cumulusci/tasks/bulkdata/extract.py
index 12c879268..5f02d1fe1 100644
--- a/cumulusci/tasks/bulkdata/extract.py
+++ b/cumulusci/tasks/bulkdata/extract.py
@@ -110,7 +110,7 @@ class ExtractData(SqlAlchemyMixin, BaseSalesforceApiTask):
 
         validate_and_inject_mapping(
             mapping=self.mapping,
-            org_config=self.org_config,
+            sf=self.sf,
             namespace=self.project_config.project__package__namespace,
             data_operation=DataOperationType.QUERY,
             inject_namespaces=self.options["inject_namespaces"],
@@ -180,7 +180,7 @@ class ExtractData(SqlAlchemyMixin, BaseSalesforceApiTask):
         # Convert relative dates to stable dates.
         if mapping.anchor_date:
             date_context = mapping.get_relative_date_context(
-                list(field_map.keys()), self.org_config
+                list(field_map.keys()), self.sf
             )
             if date_context[0] or date_context[1]:
                 record_iterator = (
diff --git a/cumulusci/tasks/bulkdata/load.py b/cumulusci/tasks/bulkdata/load.py
index c954f0c7d..020343bd3 100644
--- a/cumulusci/tasks/bulkdata/load.py
+++ b/cumulusci/tasks/bulkdata/load.py
@@ -202,7 +202,7 @@ class LoadData(SqlAlchemyMixin, BaseSalesforceApiTask):
 
         if mapping.anchor_date:
             date_context = mapping.get_relative_date_context(
-                mapping.get_load_field_list(), self.org_config
+                mapping.get_load_field_list(), self.sf
             )
         # Clamping the yield from the query ensures we do not
         # create more Bulk API batches than expected, regardless
@@ -498,7 +498,7 @@ class LoadData(SqlAlchemyMixin, BaseSalesforceApiTask):
 
         validate_and_inject_mapping(
             mapping=self.mapping,
-            org_config=self.org_config,
+            sf=self.sf,
             namespace=self.project_config.project__package__namespace,
             data_operation=DataOperationType.INSERT,
             inject_namespaces=self.options["inject_namespaces"],
diff --git a/cumulusci/tasks/bulkdata/mapping_parser.py b/cumulusci/tasks/bulkdata/mapping_parser.py
index aaecdd178..2fae820cd 100644
--- a/cumulusci/tasks/bulkdata/mapping_parser.py
+++ b/cumulusci/tasks/bulkdata/mapping_parser.py
@@ -6,9 +6,9 @@ from typing import IO, Any, Callable, Dict, List, Mapping, Optional, Union
 
 from pydantic import Field, ValidationError, root_validator, validator
 from requests.structures import CaseInsensitiveDict as RequestsCaseInsensitiveDict
+from simple_salesforce import Salesforce
 from typing_extensions import Literal
 
-from cumulusci.core.config.org_config import OrgConfig
 from cumulusci.core.exceptions import BulkDataException
 from cumulusci.tasks.bulkdata.dates import iso_to_date
 from cumulusci.tasks.bulkdata.step import DataApi, DataOperationType
@@ -139,8 +139,8 @@ class MappingStep(CCIDictModel):
 
         return fields
 
-    def get_fields_by_type(self, field_type: str, org_config: OrgConfig):
-        describe = getattr(org_config.salesforce_client, self.sf_object).describe()
+    def get_fields_by_type(self, field_type: str, sf: Salesforce):
+        describe = getattr(sf, self.sf_object).describe()
         describe = CaseInsensitiveDict(
             {entry["name"]: entry for entry in describe["fields"]}
         )
@@ -171,15 +171,15 @@ class MappingStep(CCIDictModel):
 
         return columns
 
-    def get_relative_date_context(self, fields: List[str], org_config: OrgConfig):
+    def get_relative_date_context(self, fields: List[str], sf: Salesforce):
         date_fields = [
             fields.index(f)
-            for f in self.get_fields_by_type("date", org_config)
+            for f in self.get_fields_by_type("date", sf)
             if f in self.fields
         ]
         date_time_fields = [
             fields.index(f)
-            for f in self.get_fields_by_type("datetime", org_config)
+            for f in self.get_fields_by_type("datetime", sf)
             if f in self.fields
         ]
 
@@ -388,7 +388,7 @@ class MappingStep(CCIDictModel):
 
     def validate_and_inject_namespace(
         self,
-        org_config: OrgConfig,
+        sf: Salesforce,
         namespace: Optional[str],
         operation: DataOperationType,
         inject_namespaces: bool = False,
@@ -422,10 +422,7 @@ class MappingStep(CCIDictModel):
             inject = strip = None
 
         global_describe = CaseInsensitiveDict(
-            {
-                entry["name"]: entry
-                for entry in org_config.salesforce_client.describe()["sobjects"]
-            }
+            {entry["name"]: entry for entry in sf.describe()["sobjects"]}
         )
 
         if not self._validate_sobject(global_describe, inject, strip, operation):
@@ -434,7 +431,7 @@ class MappingStep(CCIDictModel):
 
         # Validate, inject, and drop (if configured) fields.
         # By this point, we know the attribute is valid.
-        describe = getattr(org_config.salesforce_client, self.sf_object).describe()
+        describe = getattr(sf, self.sf_object).describe()
         describe = CaseInsensitiveDict(
             {entry["name"]: entry for entry in describe["fields"]}
         )
@@ -479,7 +476,7 @@ def parse_from_yaml(source: Union[str, Path, IO]) -> Dict:
 def validate_and_inject_mapping(
     *,
     mapping: Dict,
-    org_config: OrgConfig,
+    sf: Salesforce,
     namespace: str,
     data_operation: DataOperationType,
     inject_namespaces: bool,
@@ -488,7 +485,7 @@ def validate_and_inject_mapping(
 ):
     should_continue = [
         m.validate_and_inject_namespace(
-            org_config, namespace, data_operation, inject_namespaces, drop_missing
+            sf, namespace, data_operation, inject_namespaces, drop_missing
         )
         for m in mapping.values()
     ]
@@ -508,7 +505,7 @@ def validate_and_inject_mapping(
 
         # Remove any remaining lookups to dropped objects.
         for m in mapping.values():
-            describe = getattr(org_config.salesforce_client, m.sf_object).describe()
+            describe = getattr(sf, m.sf_object).describe()
             describe = {entry["name"]: entry for entry in describe["fields"]}
 
             for field in list(m.lookups.keys()):


Test patch:
diff --git a/cumulusci/tasks/bulkdata/tests/test_extract.py b/cumulusci/tasks/bulkdata/tests/test_extract.py
index ec333a785..aefba52d9 100644
--- a/cumulusci/tasks/bulkdata/tests/test_extract.py
+++ b/cumulusci/tasks/bulkdata/tests/test_extract.py
@@ -386,6 +386,7 @@ class TestExtractData:
         step = mock.Mock()
         step.get_results.return_value = [[1], [2]]
         task.session = mock.Mock()
+        task._init_task()
         task._init_mapping()
         task.mapping["Opportunity"] = mapping
         with task._init_db():
@@ -414,6 +415,7 @@ class TestExtractData:
         )
         task.session = mock.Mock()
         task._sql_bulk_insert_from_records = mock.Mock()
+        task._init_task()
         task._import_results(mapping, step)
 
         task.session.connection.assert_called_once_with()
@@ -524,6 +526,7 @@ class TestExtractData:
         task._convert_lookups_to_id = mock.Mock()
         task.metadata = mock.MagicMock()
 
+        task._init_task()
         task._init_mapping()
         task._map_autopks()
 
@@ -740,6 +743,7 @@ class TestExtractData:
         )
         task.org_config._is_person_accounts_enabled = False
 
+        task._init_task()
         task._init_mapping()
         assert "Insert Households" in task.mapping
 
@@ -757,6 +761,7 @@ class TestExtractData:
         )
         task.org_config._is_person_accounts_enabled = True
 
+        task._init_task()
         task._init_mapping()
         assert "Insert Households" in task.mapping
 
@@ -780,11 +785,12 @@ class TestExtractData:
         )
         t.org_config._is_person_accounts_enabled = True
 
+        t._init_task()
         t._init_mapping()
 
         validate_and_inject_mapping.assert_called_once_with(
             mapping=t.mapping,
-            org_config=t.org_config,
+            sf=t.sf,
             namespace=t.project_config.project__package__namespace,
             data_operation=DataOperationType.QUERY,
             inject_namespaces=True,
diff --git a/cumulusci/tasks/bulkdata/tests/test_load.py b/cumulusci/tasks/bulkdata/tests/test_load.py
index 0bcad4ec1..51c63624b 100644
--- a/cumulusci/tasks/bulkdata/tests/test_load.py
+++ b/cumulusci/tasks/bulkdata/tests/test_load.py
@@ -30,7 +30,11 @@ from cumulusci.tasks.bulkdata.tests.utils import (
     FakeBulkAPIDmlOperation,
     _make_task,
 )
-from cumulusci.tests.util import assert_max_memory_usage, mock_describe_calls
+from cumulusci.tests.util import (
+    CURRENT_SF_API_VERSION,
+    assert_max_memory_usage,
+    mock_describe_calls,
+)
 from cumulusci.utils import temporary_dir
 
 
@@ -42,7 +46,7 @@ class TestLoadData(unittest.TestCase):
     def test_run(self, dml_mock):
         responses.add(
             method="GET",
-            url="https://example.com/services/data/v46.0/query/?q=SELECT+Id+FROM+RecordType+WHERE+SObjectType%3D%27Account%27AND+DeveloperName+%3D+%27HH_Account%27+LIMIT+1",
+            url=f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/query/?q=SELECT+Id+FROM+RecordType+WHERE+SObjectType%3D%27Account%27AND+DeveloperName+%3D+%27HH_Account%27+LIMIT+1",
             body=json.dumps({"records": [{"Id": "1"}]}),
             status=200,
         )
@@ -186,7 +190,7 @@ class TestLoadData(unittest.TestCase):
     def test_run__sql(self, dml_mock):
         responses.add(
             method="GET",
-            url="https://example.com/services/data/v46.0/query/?q=SELECT+Id+FROM+RecordType+WHERE+SObjectType%3D%27Account%27AND+DeveloperName+%3D+%27HH_Account%27+LIMIT+1",
+            url=f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/query/?q=SELECT+Id+FROM+RecordType+WHERE+SObjectType%3D%27Account%27AND+DeveloperName+%3D+%27HH_Account%27+LIMIT+1",
             body=json.dumps({"records": [{"Id": "1"}]}),
             status=200,
         )
@@ -291,11 +295,12 @@ class TestLoadData(unittest.TestCase):
             },
         )
 
+        t._init_task()
         t._init_mapping()
 
         validate_and_inject_mapping.assert_called_once_with(
             mapping=t.mapping,
-            org_config=t.org_config,
+            sf=t.sf,
             namespace=t.project_config.project__package__namespace,
             data_operation=DataOperationType.INSERT,
             inject_namespaces=True,
@@ -312,6 +317,7 @@ class TestLoadData(unittest.TestCase):
         )
 
         mock_describe_calls()
+        task._init_task()
         task._init_mapping()
 
         model = mock.Mock()
@@ -429,8 +435,7 @@ class TestLoadData(unittest.TestCase):
         task = _make_task(
             LoadData, {"options": {"database_url": "sqlite://", "mapping": "test.yml"}}
         )
-        task.sf = mock.Mock()
-
+        task._init_task()
         mapping = MappingStep(
             sf_object="Contact",
             action="insert",
@@ -1503,6 +1508,7 @@ class TestLoadData(unittest.TestCase):
         )
         mock_describe_calls()
 
+        task._init_task()
         task._init_mapping()
         task.mapping["Insert Households"]["fields"]["RecordTypeId"] = "RecordTypeId"
         with task._init_db():
@@ -1532,7 +1538,7 @@ class TestLoadData(unittest.TestCase):
     def test_run__autopk(self, dml_mock):
         responses.add(
             method="GET",
-            url="https://example.com/services/data/v46.0/query/?q=SELECT+Id+FROM+RecordType+WHERE+SObjectType%3D%27Account%27AND+DeveloperName+%3D+%27HH_Account%27+LIMIT+1",
+            url=f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/query/?q=SELECT+Id+FROM+RecordType+WHERE+SObjectType%3D%27Account%27AND+DeveloperName+%3D+%27HH_Account%27+LIMIT+1",
             body=json.dumps({"records": [{"Id": "1"}]}),
             status=200,
         )
@@ -1595,6 +1601,7 @@ class TestLoadData(unittest.TestCase):
             {"options": {"database_url": "sqlite://", "mapping": mapping_path}},
         )
         mock_describe_calls()
+        task._init_task()
         task._init_mapping()
         assert (
             task.mapping["Insert Accounts"]["lookups"]["ParentId"]["after"]
@@ -1623,6 +1630,7 @@ class TestLoadData(unittest.TestCase):
             {"options": {"database_url": "sqlite://", "mapping": mapping_path}},
         )
         mock_describe_calls()
+        task._init_task()
         task._init_mapping()
 
         class FakeModel:
@@ -1645,6 +1653,7 @@ class TestLoadData(unittest.TestCase):
             {"options": {"database_url": "sqlite://", "mapping": mapping_path}},
         )
         mock_describe_calls()
+        task._init_task()
         task._init_mapping()
 
         class FakeModel:
@@ -2185,7 +2194,7 @@ class TestLoadData(unittest.TestCase):
     def test_load_memory_usage(self):
         responses.add(
             method="GET",
-            url="https://example.com/services/data/v46.0/query/?q=SELECT+Id+FROM+RecordType+WHERE+SObjectType%3D%27Account%27AND+DeveloperName+%3D+%27HH_Account%27+LIMIT+1",
+            url=f"https://example.com/services/data/v{CURRENT_SF_API_VERSION}/query/?q=SELECT+Id+FROM+RecordType+WHERE+SObjectType%3D%27Account%27AND+DeveloperName+%3D+%27HH_Account%27+LIMIT+1",
             body=json.dumps({"records": [{"Id": "1"}]}),
             status=200,
         )
diff --git a/cumulusci/tasks/bulkdata/tests/test_mapping_parser.py b/cumulusci/tasks/bulkdata/tests/test_mapping_parser.py
index c19ff6332..f8de541d9 100644
--- a/cumulusci/tasks/bulkdata/tests/test_mapping_parser.py
+++ b/cumulusci/tasks/bulkdata/tests/test_mapping_parser.py
@@ -176,8 +176,8 @@ class TestMappingParser:
             anchor_date="2020-07-01",
         )
 
-        org_config = mock.Mock()
-        org_config.salesforce_client.Account.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.Account.describe.return_value = {
             "fields": [
                 {"name": "Some_Date__c", "type": "date"},
                 {"name": "Some_Datetime__c", "type": "datetime"},
@@ -186,14 +186,14 @@ class TestMappingParser:
         }
 
         assert mapping.get_relative_date_context(
-            mapping.get_load_field_list(), org_config
+            mapping.get_load_field_list(), salesforce_client
         ) == ([0], [1], date.today())
 
     def test_get_relative_date_e2e(self):
         base_path = Path(__file__).parent / "mapping_v1.yml"
         mapping = parse_from_yaml(base_path)
-        org_config = mock.Mock()
-        org_config.salesforce_client.Contact.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.Contact.describe.return_value = {
             "fields": [
                 {"name": "Some_Date__c", "type": "date"},
                 {"name": "Some_Datetime__c", "type": "datetime"},
@@ -205,7 +205,7 @@ class TestMappingParser:
             {"Some_Date__c": "Some_Date__c", "Some_Datetime__c": "Some_Datetime__c"}
         )
         assert contacts_mapping.get_relative_date_context(
-            contacts_mapping.get_load_field_list(), org_config
+            contacts_mapping.get_load_field_list(), salesforce_client
         ) == (
             [3],
             [4],
@@ -441,16 +441,16 @@ class TestMappingParser:
             )
         )["Insert Accounts"]
 
-        org_config = mock.Mock()
-        org_config.salesforce_client.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.describe.return_value = {
             "sobjects": [{"name": "Account", "createable": True}]
         }
-        org_config.salesforce_client.Account.describe.return_value = {
+        salesforce_client.Account.describe.return_value = {
             "fields": [{"name": "ns__Test__c", "createable": True}]
         }
 
         assert ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT, inject_namespaces=True
+            salesforce_client, "ns", DataOperationType.INSERT, inject_namespaces=True
         )
 
         ms._validate_sobject.assert_called_once_with(
@@ -507,11 +507,11 @@ class TestMappingParser:
             )
         )["Insert Accounts"]
 
-        org_config = mock.Mock()
-        org_config.salesforce_client.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.describe.return_value = {
             "sobjects": [{"name": "Account", "createable": True}]
         }
-        org_config.salesforce_client.Account.describe.return_value = {
+        salesforce_client.Account.describe.return_value = {
             "fields": [
                 {"name": "Name", "createable": True},
                 {"name": "ns__Lookup__c", "updateable": False, "createable": True},
@@ -519,7 +519,7 @@ class TestMappingParser:
         }
 
         assert ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT, inject_namespaces=True
+            salesforce_client, "ns", DataOperationType.INSERT, inject_namespaces=True
         )
 
         ms._validate_sobject.assert_called_once_with(
@@ -577,15 +577,15 @@ class TestMappingParser:
             sf_object="Test__c", fields=["Field__c"], action=DataOperationType.INSERT
         )
 
-        org_config = mock.Mock()
-        org_config.salesforce_client.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.describe.return_value = {
             "sobjects": [{"name": "Test__c", "createable": True}]
         }
-        org_config.salesforce_client.Test__c.describe.return_value = {
+        salesforce_client.Test__c.describe.return_value = {
             "fields": [{"name": "Field__c", "createable": True}]
         }
         assert ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT
+            salesforce_client, "ns", DataOperationType.INSERT
         )
 
         ms._validate_sobject.assert_called_once_with(
@@ -631,15 +631,15 @@ class TestMappingParser:
             sf_object="Test__c", fields=["Name"], action=DataOperationType.INSERT
         )
 
-        org_config = mock.Mock()
-        org_config.salesforce_client.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.describe.return_value = {
             "sobjects": [{"name": "Test__c", "createable": False}]
         }
-        org_config.salesforce_client.Test__c.describe.return_value = {
+        salesforce_client.Test__c.describe.return_value = {
             "fields": [{"name": "Name", "createable": True}]
         }
         assert not ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT
+            salesforce_client, "ns", DataOperationType.INSERT
         )
 
         ms._validate_sobject.assert_called_once_with(
@@ -666,15 +666,15 @@ class TestMappingParser:
             sf_object="Test__c", fields=["Name"], action=DataOperationType.INSERT
         )
 
-        org_config = mock.Mock()
-        org_config.salesforce_client.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.describe.return_value = {
             "sobjects": [{"name": "Test__c", "createable": True}]
         }
-        org_config.salesforce_client.Test__c.describe.return_value = {
+        salesforce_client.Test__c.describe.return_value = {
             "fields": [{"name": "Name", "createable": False}]
         }
         assert not ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT
+            salesforce_client, "ns", DataOperationType.INSERT
         )
 
         ms._validate_sobject.assert_called_once_with(
@@ -721,18 +721,18 @@ class TestMappingParser:
             )
         )["Insert Accounts"]
 
-        org_config = mock.Mock()
-        org_config.salesforce_client.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.describe.return_value = {
             "sobjects": [{"name": "Account", "createable": True}]
         }
-        org_config.salesforce_client.Account.describe.return_value = {
+        salesforce_client.Account.describe.return_value = {
             "fields": [
                 {"name": "Name", "createable": True},
                 {"name": "Lookup__c", "updateable": True, "createable": False},
             ]
         }
         assert not ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT
+            salesforce_client, "ns", DataOperationType.INSERT
         )
 
         ms._validate_sobject.assert_called_once_with(
@@ -802,18 +802,18 @@ class TestMappingParser:
             )
         )["Insert Accounts"]
 
-        org_config = mock.Mock()
-        org_config.salesforce_client.describe.return_value = {
+        salesforce_client = mock.Mock()
+        salesforce_client.describe.return_value = {
             "sobjects": [{"name": "Account", "createable": True}]
         }
-        org_config.salesforce_client.Account.describe.return_value = {
+        salesforce_client.Account.describe.return_value = {
             "fields": [
                 {"name": "Name", "createable": True},
                 {"name": "Lookup__c", "updateable": False, "createable": True},
             ]
         }
         assert not ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT
+            salesforce_client, "ns", DataOperationType.INSERT
         )
 
         ms._validate_sobject.assert_called_once_with(
@@ -875,7 +875,7 @@ class TestMappingParser:
         with pytest.raises(BulkDataException):
             validate_and_inject_mapping(
                 mapping=mapping,
-                org_config=org_config,
+                sf=org_config.salesforce_client,
                 namespace=None,
                 data_operation=DataOperationType.INSERT,
                 inject_namespaces=False,
@@ -896,7 +896,7 @@ class TestMappingParser:
 
         validate_and_inject_mapping(
             mapping=mapping,
-            org_config=org_config,
+            sf=org_config.salesforce_client,
             namespace=None,
             data_operation=DataOperationType.INSERT,
             inject_namespaces=False,
@@ -922,7 +922,7 @@ class TestMappingParser:
 
         validate_and_inject_mapping(
             mapping=mapping,
-            org_config=org_config,
+            sf=org_config.salesforce_client,
             namespace=None,
             data_operation=DataOperationType.INSERT,
             inject_namespaces=False,
@@ -955,7 +955,7 @@ class TestMappingParser:
         with pytest.raises(BulkDataException):
             validate_and_inject_mapping(
                 mapping=mapping,
-                org_config=org_config,
+                sf=org_config.salesforce_client,
                 namespace=None,
                 data_operation=DataOperationType.INSERT,
                 inject_namespaces=False,
@@ -980,7 +980,10 @@ class TestMappingParser:
         )
 
         assert ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT, inject_namespaces=True
+            org_config.salesforce_client,
+            "ns",
+            DataOperationType.INSERT,
+            inject_namespaces=True,
         )
 
         assert list(ms.fields.keys()) == ["ns__Description__c"]
@@ -1003,7 +1006,10 @@ class TestMappingParser:
         )
 
         assert ms.validate_and_inject_namespace(
-            org_config, "ns", DataOperationType.INSERT, inject_namespaces=True
+            org_config.salesforce_client,
+            "ns",
+            DataOperationType.INSERT,
+            inject_namespaces=True,
         )
 
         assert list(ms.fields.keys()) == ["History__c"]
@@ -1025,7 +1031,7 @@ class TestMappingParser:
 
         validate_and_inject_mapping(
             mapping=mapping,
-            org_config=org_config,
+            sf=org_config.salesforce_client,
             namespace=None,
             data_operation=DataOperationType.QUERY,
             inject_namespaces=False,
@@ -1099,7 +1105,7 @@ class TestMappingLookup:
 
         validate_and_inject_mapping(
             mapping=mapping,
-            org_config=org_config,
+            sf=org_config.salesforce_client,
             namespace=None,
             data_operation=DataOperationType.INSERT,
             inject_namespaces=False,
diff --git a/cumulusci/tasks/bulkdata/tests/utils.py b/cumulusci/tasks/bulkdata/tests/utils.py
index c275f6e6d..14401c7f4 100644
--- a/cumulusci/tasks/bulkdata/tests/utils.py
+++ b/cumulusci/tasks/bulkdata/tests/utils.py
@@ -7,13 +7,18 @@ from cumulusci.tasks.bulkdata.step import (
 )
 from cumulusci.tests import util as cci_test_utils
 
+CURRENT_SF_API_VERSION = "48.0"
+
 
 def _make_task(task_class, task_config):
     task_config = TaskConfig(task_config)
     universal_config = UniversalConfig()
     project_config = BaseProjectConfig(
         universal_config,
-        config={"noyaml": True, "project": {"package": {"api_version": "46.0"}}},
+        config={
+            "noyaml": True,
+            "project": {"package": {"api_version": CURRENT_SF_API_VERSION}},
+        },
     )
     keychain = BaseProjectKeychain(project_config, "")
     project_config.set_keychain(keychain)
@@ -31,6 +36,9 @@ class FakeBulkAPI:
 
     next_job_id = 0
     next_batch_id = 0
+    endpoint = (
+        f"https://example.my.salesforce.com/services/async/{CURRENT_SF_API_VERSION}"
+    )
 
     @classmethod
     def create_job(cls, *args, **kwargs):
diff --git a/cumulusci/tests/util.py b/cumulusci/tests/util.py
index 6fffc6dda..ec9c0c406 100644
--- a/cumulusci/tests/util.py
+++ b/cumulusci/tests/util.py
@@ -19,7 +19,7 @@ from cumulusci.core.config import (
     UniversalConfig,
 )
 from cumulusci.core.keychain import BaseProjectKeychain
-from cumulusci.tasks.bulkdata.tests import utils as bulkdata_utils
+from cumulusci.tasks.bulkdata.tests.utils import CURRENT_SF_API_VERSION, FakeBulkAPI
 
 
 def random_sha():
@@ -153,11 +153,18 @@ def big_objs(traced_only=False):
         print(type(obj), size, tracemalloc.get_object_traceback(obj))
 
 
+class FakeSFSobjectProxy:
+    def __init__(self, describe_data):
+        self.describe_data = describe_data
+
+    def describe(self):
+        return self.describe_data
+
+
 class FakeSF:
-    """Extremely simplistic mock of the Simple-Salesforce API
+    """Simplistic mock of the Simple-Salesforce API
 
     Can be improved as needed over time.
-    In particular, __getattr__ is not implemented yet.
     """
 
     fakes = {}
@@ -170,11 +177,14 @@ class FakeSF:
         return "47.0"
 
     def _get_json(self, fake_dataset):
-        self.fakes[fake_dataset] = self.fakes.get("sobjname", None) or read_mock(
-            fake_dataset
+        self.fakes[fake_dataset] = self.fakes.get(fake_dataset, None) or json.loads(
+            read_mock(fake_dataset)
         )
         return self.fakes[fake_dataset]
 
+    def __getattr__(self, name):
+        return FakeSFSobjectProxy(self._get_json(name))
+
 
 def read_mock(name: str):
     base_path = Path(__file__).parent.parent / "tasks/bulkdata/tests"
@@ -231,7 +241,7 @@ def mock_salesforce_client(task, *, is_person_accounts_enabled=False):
 
     def _init_task():
         real_init()
-        task.bulk = bulkdata_utils.FakeBulkAPI()
+        task.bulk = FakeBulkAPI()
         task.sf = salesforce_client
 
     with mock.patch(
@@ -297,3 +307,8 @@ class FakeUnreliableRequestHandler:
 
     def real_reliable_request_callback(self, request):
         return self.response
+
+
+CURRENT_SF_API_VERSION = (
+    CURRENT_SF_API_VERSION  # quiet linter and export to other modules
+)