Beispiel #1
0
 def test_run__ignore_error_throttling(self, dml_mock, query_mock):
     mock_describe_calls()
     task = _make_task(
         DeleteData,
         {
             "options": {
                 "objects": "Contact",
                 "ignore_row_errors": "true",
                 "hardDelete": "true",
             }
         },
     )
     query_mock.return_value.get_results.return_value = iter(
         ["001000000000000", "001000000000001"] * 15)
     query_mock.return_value.job_result = DataOperationJobResult(
         DataOperationStatus.SUCCESS, [], 2, 0)
     dml_mock.return_value.get_results.return_value = iter([
         DataOperationResult("001000000000000", True, None),
         DataOperationResult("001000000000001", False, None),
     ] * 15)
     dml_mock.return_value.job_result = DataOperationJobResult(
         DataOperationStatus.SUCCESS, [], 2, 0)
     with mock.patch.object(task.logger, "warning") as warning:
         task()
     assert len(warning.mock_calls) == task.row_warning_limit + 1 == 11
     assert "warnings suppressed" in str(warning.mock_calls[-1])
    def test_validate_and_inject_mapping_works_case_insensitively(self):
        mock_describe_calls()
        mapping = parse_from_yaml(
            StringIO(
                (
                    "Insert Accounts:\n  sf_object: account\n  table: account\n  fields:\n    - name\n"
                    "Insert Contacts:\n  sf_object: contact\n  table: contact\n  fields:\n    - fIRSTnAME\n  lookups:\n    accountid:\n      table: account"
                )
            )
        )
        org_config = DummyOrgConfig(
            {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
        )

        assert mapping["Insert Accounts"].sf_object != "Account"
        assert mapping["Insert Accounts"].sf_object == "account"
        assert "name" in mapping["Insert Accounts"].fields
        assert "Name" not in mapping["Insert Accounts"].fields

        validate_and_inject_mapping(
            mapping=mapping,
            org_config=org_config,
            namespace=None,
            data_operation=DataOperationType.INSERT,
            inject_namespaces=False,
            drop_missing=False,
        )
        assert mapping["Insert Accounts"].sf_object == "Account"
        assert mapping["Insert Accounts"].sf_object != "account"
        assert "Name" in mapping["Insert Accounts"].fields
        assert "name" not in mapping["Insert Accounts"].fields
Beispiel #3
0
    def test_map_autopks(self):
        mock_describe_calls()
        base_path = os.path.dirname(__file__)
        mapping_path = os.path.join(base_path, self.mapping_file_v2)
        mock_describe_calls()

        task = _make_task(
            ExtractData,
            {
                "options": {
                    "database_url": "sqlite://",  # in memory
                    "mapping": mapping_path,
                }
            },
        )
        task._convert_lookups_to_id = mock.Mock()
        task.metadata = mock.MagicMock()

        task._init_mapping()
        task._map_autopks()

        task._convert_lookups_to_id.assert_called_once_with(
            task.mapping["Insert Contacts"], ["AccountId"]
        )
        task.metadata.tables.__getitem__.return_value.drop.assert_has_calls(
            [mock.call(), mock.call()]
        )
        task.metadata.tables.__getitem__.assert_has_calls(
            [mock.call("contacts_sf_ids"), mock.call("households_sf_ids")],
            any_order=True,
        )
    def test_validate_and_inject_mapping_removes_lookups_with_drop_missing(self):
        mock_describe_calls()
        mapping = parse_from_yaml(
            StringIO(
                (
                    "Insert Accounts:\n  sf_object: NotAccount\n  table: Account\n  fields:\n    - Nonsense__c\n"
                    "Insert Contacts:\n  sf_object: Contact\n  table: Contact\n  lookups:\n    AccountId:\n      table: Account"
                )
            )
        )
        org_config = DummyOrgConfig(
            {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
        )

        validate_and_inject_mapping(
            mapping=mapping,
            org_config=org_config,
            namespace=None,
            data_operation=DataOperationType.INSERT,
            inject_namespaces=False,
            drop_missing=True,
        )

        assert "Insert Accounts" not in mapping
        assert "Insert Contacts" in mapping
        assert "AccountId" not in mapping["Insert Contacts"].lookups
Beispiel #5
0
    def test_import_results__relative_dates(self):
        mock_describe_calls()
        task = _make_task(
            ExtractData,
            {"options": {"database_url": "sqlite://", "mapping": "mapping.yml"}},
        )

        mapping = MappingStep(
            sf_object="Opportunity",
            fields={"Id": "sf_id", "CloseDate": "CloseDate"},
            anchor_date="2020-07-01",
        )
        step = mock.Mock()
        step.get_results.return_value = iter(
            [["006000000000001", (date.today() + timedelta(days=9)).isoformat()]]
        )
        task.session = mock.Mock()
        task._sql_bulk_insert_from_records = mock.Mock()
        task._import_results(mapping, step)

        task.session.connection.assert_called_once_with()
        step.get_results.assert_called_once_with()
        task._sql_bulk_insert_from_records.assert_called_once_with(
            connection=task.session.connection.return_value,
            table=mapping.table,
            columns=["sf_id", "CloseDate"],
            record_iterable=mock.ANY,
        )

        result = list(
            task._sql_bulk_insert_from_records.call_args_list[0][1]["record_iterable"]
        )
        assert result == [["006000000000001", "2020-07-10"]]
Beispiel #6
0
    def test_import_results__no_columns(self):  # , query_op_mock):
        base_path = os.path.dirname(__file__)
        mapping_path = os.path.join(base_path, self.mapping_file_v1)
        mock_describe_calls()

        task = _make_task(
            ExtractData,
            {"options": {"database_url": "sqlite://", "mapping": mapping_path}},
        )

        mapping = MappingStep(
            sf_object="Opportunity",
            table="Opportunity",
            fields={},
            lookups={},
        )
        step = mock.Mock()
        step.get_results.return_value = [[1], [2]]
        task.session = mock.Mock()
        task._init_mapping()
        task.mapping["Opportunity"] = mapping
        with task._init_db():
            task._import_results(mapping, step)
            output_Opportunties = list(
                task.session.execute("select * from Opportunity")
            )
            assert output_Opportunties == [(1,), (2,)]
    def test_validate_and_inject_mapping_throws_exception_required_lookup_dropped(self):
        mock_describe_calls()

        # This test uses a bit of gimmickry to validate exception behavior on dropping a required lookup.
        # Since mapping_parser identifies target objects via the `table` clause rather than the actual schema,
        # which makes us resilient to polymorphic lookups, we'll pretend the non-nillable `Id` field is a lookup.
        mapping = parse_from_yaml(
            StringIO(
                (
                    "Insert Accounts:\n  sf_object: NotAccount\n  table: Account\n  fields:\n    - Nonsense__c\n"
                    "Insert Contacts:\n  sf_object: Contact\n  table: Contact\n  lookups:\n    Id:\n      table: Account"
                )
            )
        )
        org_config = DummyOrgConfig(
            {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
        )

        with pytest.raises(BulkDataException):
            validate_and_inject_mapping(
                mapping=mapping,
                org_config=org_config,
                namespace=None,
                data_operation=DataOperationType.INSERT,
                inject_namespaces=False,
                drop_missing=True,
            )
    def test_validate_and_inject_mapping_queries_is_person_account_field(self):
        mock_describe_calls()
        mapping = parse_from_yaml(
            StringIO(
                (
                    "Insert Accounts:\n  sf_object: Account\n  table: Account\n  fields:\n    - Description__c\n"
                    "Insert Contacts:\n  sf_object: Contact\n  table: Contact\n  lookups:\n    AccountId:\n      table: Account"
                )
            )
        )
        org_config = DummyOrgConfig(
            {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
        )

        validate_and_inject_mapping(
            mapping=mapping,
            org_config=org_config,
            namespace=None,
            data_operation=DataOperationType.QUERY,
            inject_namespaces=False,
            drop_missing=True,
            org_has_person_accounts_enabled=True,
        )

        assert "Insert Accounts" in mapping
        assert "Insert Contacts" in mapping
        assert "IsPersonAccount" in mapping["Insert Accounts"]["fields"]
        assert "IsPersonAccount" in mapping["Insert Contacts"]["fields"]
Beispiel #9
0
 def test_run__job_error_query(self, dml_mock, query_mock):
     mock_describe_calls()
     task = _make_task(DeleteData, {"options": {"objects": "Contact"}})
     query_mock.return_value.get_results.return_value = iter(
         ["001000000000000", "001000000000001"])
     query_mock.return_value.job_result = DataOperationJobResult(
         DataOperationStatus.JOB_FAILURE, [], 0, 0)
     with self.assertRaises(BulkDataException):
         task()
Beispiel #10
0
    def test_validate_and_inject_namespace__missing_object(self):
        mock_describe_calls()
        task = _make_task(
            DeleteData, {"options": {
                "objects": "ApexTestQueueItem,Account"
            }})
        # ApexTestQueueItem is not deletable

        with pytest.raises(BulkDataException):
            task._validate_and_inject_namespace()
Beispiel #11
0
    def test_validate_and_inject_namespace__standard(self):
        mock_describe_calls()

        task = _make_task(DeleteData,
                          {"options": {
                              "objects": "Contact,Account"
                          }})

        task._validate_and_inject_namespace()

        assert task.sobjects == ["Contact", "Account"]
Beispiel #12
0
    def test_insert_dml_operation__delete(self):
        mock_describe_calls()
        task = _make_task(
            LoadData,
            {
                "options": {
                    "database_url": "sqlite:///test.db",
                    "mapping": "mapping.yml",
                }
            },
        )
        task.project_config.project__package__api_version = "48.0"
        task._init_task()

        responses.add(
            responses.DELETE,
            url="https://example.com/services/data/v48.0/composite/sobjects?ids=003000000000001,003000000000002",
            json=[
                {"id": "003000000000001", "success": True},
                {"id": "003000000000002", "success": True},
            ],
            status=200,
        )
        responses.add(
            responses.DELETE,
            url="https://example.com/services/data/v48.0/composite/sobjects?ids=003000000000003",
            json=[{"id": "003000000000003", "success": True}],
            status=200,
        )

        recs = [["003000000000001"], ["003000000000002"], ["003000000000003"]]

        dml_op = RestApiDmlOperation(
            sobject="Contact",
            operation=DataOperationType.DELETE,
            api_options={"batch_size": 2},
            context=task,
            fields=["Id"],
        )

        dml_op.start()
        dml_op.load_records(iter(recs))
        dml_op.end()

        assert dml_op.job_result == DataOperationJobResult(
            DataOperationStatus.SUCCESS, [], 3, 0
        )
        assert list(dml_op.get_results()) == [
            DataOperationResult("003000000000001", True, ""),
            DataOperationResult("003000000000002", True, ""),
            DataOperationResult("003000000000003", True, ""),
        ]
Beispiel #13
0
    def test_run__person_accounts_enabled(self, query_op_mock):
        base_path = os.path.dirname(__file__)
        mapping_path = os.path.join(base_path, self.mapping_file_v1)
        mock_describe_calls()

        with temporary_dir() as d:
            tmp_db_path = os.path.join(d, "testdata.db")

            task = _make_task(
                ExtractData,
                {
                    "options": {
                        "database_url": f"sqlite:///{tmp_db_path}",
                        "mapping": mapping_path,
                    }
                },
            )
            task.bulk = mock.Mock()
            task.sf = mock.Mock()
            task.org_config._is_person_accounts_enabled = True

            mock_query_households = MockBulkQueryOperation(
                sobject="Account",
                api_options={},
                context=task,
                query="SELECT Id, IsPersonAccount FROM Account",
            )
            mock_query_contacts = MockBulkQueryOperation(
                sobject="Contact",
                api_options={},
                context=task,
                query="SELECT Id, FirstName, LastName, Email, IsPersonAccount, AccountId FROM Contact",
            )
            mock_query_households.results = [["1", "false"]]
            mock_query_contacts.results = [
                ["2", "First", "Last", "*****@*****.**", "true", "1"]
            ]

            query_op_mock.side_effect = [mock_query_households, mock_query_contacts]

            task()
            with create_engine(task.options["database_url"]).connect() as conn:

                household = next(conn.execute("select * from households"))
                assert "1" == household.sf_id
                assert "false" == household.IsPersonAccount
                assert "HH_Account" == household.record_type

                contact = next(conn.execute("select * from contacts"))
                assert "2" == contact.sf_id
                assert "true" == contact.IsPersonAccount
                assert "1" == contact.household_id
Beispiel #14
0
 def test_run__job_error_delete(self, dml_mock, query_mock):
     mock_describe_calls()
     task = _make_task(DeleteData, {"options": {"objects": "Contact"}})
     query_mock.return_value.get_results.return_value = iter(
         ["001000000000000", "001000000000001"])
     query_mock.return_value.job_result = DataOperationJobResult(
         DataOperationStatus.SUCCESS, [], 2, 0)
     dml_mock.return_value.get_results.return_value = iter([
         DataOperationResult("001000000000000", True, None),
         DataOperationResult("001000000000001", False, None),
     ])
     with self.assertRaises(BulkDataException):
         task()
Beispiel #15
0
    def test_run__ignore_error(self, dml_mock, query_mock):
        mock_describe_calls()
        task = _make_task(
            DeleteData,
            {
                "options": {
                    "objects": "Contact",
                    "ignore_row_errors": "true",
                    "hardDelete": "true",
                }
            },
        )
        query_mock.return_value.get_results.return_value = iter(
            ["001000000000000", "001000000000001"])
        query_mock.return_value.job_result = DataOperationJobResult(
            DataOperationStatus.SUCCESS, [], 2, 0)
        dml_mock.return_value.get_results.return_value = iter([
            DataOperationResult("001000000000000", True, None),
            DataOperationResult("001000000000001", False, None),
        ])
        dml_mock.return_value.job_result = DataOperationJobResult(
            DataOperationStatus.SUCCESS, [], 2, 0)
        with mock.patch.object(task.logger, "warning") as warning:
            task()
        assert len(warning.mock_calls) == 1
        query_mock.assert_called_once_with(
            sobject="Contact",
            api_options={},
            context=task,
            query="SELECT Id FROM Contact",
            api=DataApi.SMART,
            fields=["Id"],
        )
        query_mock.return_value.query.assert_called_once()
        query_mock.return_value.get_results.assert_called_once()

        dml_mock.assert_called_once_with(
            sobject="Contact",
            operation=DataOperationType.HARD_DELETE,
            api_options={},
            context=task,
            fields=["Id"],
            api=DataApi.SMART,
            volume=2,
        )
        dml_mock.return_value.start.assert_called_once()
        dml_mock.return_value.end.assert_called_once()
        dml_mock.return_value.load_records.assert_called_once()
        dml_mock.return_value.get_results.assert_called_once()
Beispiel #16
0
    def test_init_mapping_org_has_person_accounts_enabled(self):
        base_path = os.path.dirname(__file__)
        mapping_path = os.path.join(base_path, self.mapping_file_v1)
        mock_describe_calls()
        task = _make_task(
            ExtractData,
            {"options": {"database_url": "sqlite:///", "mapping": mapping_path}},
        )
        task.org_config._is_person_accounts_enabled = True

        task._init_mapping()
        assert "Insert Households" in task.mapping

        # Person Accounts should not be added to mapping
        self.assert_person_accounts_in_mapping(task.mapping, True)
Beispiel #17
0
    def test_run__v2__person_accounts_disabled(self, query_op_mock):
        base_path = os.path.dirname(__file__)
        mapping_path = os.path.join(base_path, self.mapping_file_v2)
        mock_describe_calls()

        with TemporaryDirectory() as t:
            task = _make_task(
                ExtractData,
                {
                    "options": {
                        "database_url": f"sqlite:///{t}/temp_db",  # in memory
                        "mapping": mapping_path,
                    }
                },
            )
            task.bulk = mock.Mock()
            task.sf = mock.Mock()
            task.org_config._is_person_accounts_enabled = False

            mock_query_households = MockBulkQueryOperation(
                sobject="Account",
                api_options={},
                context=task,
                query="SELECT Id, Name FROM Account",
            )
            mock_query_contacts = MockBulkQueryOperation(
                sobject="Contact",
                api_options={},
                context=task,
                query="SELECT Id, FirstName, LastName, Email, AccountId FROM Contact",
            )
            mock_query_households.results = [["1", "TestHousehold"]]
            mock_query_contacts.results = [
                ["2", "First", "Last", "*****@*****.**", "1"]
            ]

            query_op_mock.side_effect = [mock_query_households, mock_query_contacts]

            task()
            with create_engine(task.options["database_url"]).connect() as conn:
                household = next(conn.execute("select * from households"))
                assert household.name == "TestHousehold"
                assert not hasattr(household, "IsPersonAccount")
                assert household.record_type == "HH_Account"

                contact = next(conn.execute("select * from contacts"))
                assert contact.household_id == "1"
                assert not hasattr(contact, "IsPersonAccount")
Beispiel #18
0
    def test_insert_dml_operation__boolean_conversion__fails(self):
        mock_describe_calls()
        task = _make_task(
            LoadData,
            {
                "options": {
                    "database_url": "sqlite:///test.db",
                    "mapping": "mapping.yml",
                }
            },
        )
        task.project_config.project__package__api_version = "48.0"
        task._init_task()

        responses.add(
            responses.POST,
            url="https://example.com/services/data/v48.0/composite/sobjects",
            json=[
                {"id": "003000000000001", "success": True},
                {"id": "003000000000002", "success": True},
            ],
            status=200,
        )
        responses.add(
            responses.POST,
            url="https://example.com/services/data/v48.0/composite/sobjects",
            json=[{"id": "003000000000003", "success": True}],
            status=200,
        )

        recs = [
            ["Narvaez", "xyzzy"],
        ]

        dml_op = RestApiDmlOperation(
            sobject="Contact",
            operation=DataOperationType.INSERT,
            context=task,
            api_options={},
            fields=["LastName", "IsEmailBounced"],
        )

        dml_op.start()
        with pytest.raises(BulkDataException) as e:
            dml_op.load_records(iter(recs))
        assert "xyzzy" in str(e.value)
Beispiel #19
0
    def test_import_results__autopk(self, create_task_fixture):
        mock_describe_calls()
        base_path = os.path.dirname(__file__)
        mapping_path = os.path.join(base_path, self.mapping_file_vanilla)
        with temporary_dir() as d:
            task = create_task_fixture(
                ExtractData,
                {"database_url": f"sqlite:///{d}/temp.db", "mapping": mapping_path},
            )

            extracted_records = {
                # ['Name', 'Description', 'BillingStreet', 'BillingCity', 'BillingState', 'BillingPostalCode', 'BillingCountry', 'ParentId', 'ShippingStreet', 'ShippingCity', 'ShippingState', 'ShippingPostalCode', 'ShippingCountry', 'Phone', 'Fax', 'Website', 'NumberOfEmployees', 'AccountNumber', 'Site', 'Type', 'IsPersonAccount']
                "Account": [
                    ["001002", "Account1"] + [""] * 20 + [False],
                    ["001003", "Account2"] + [""] * 20 + [False],
                ],
                # Salutation|FirstName|LastName|Email|Phone|MobilePhone|OtherPhone|HomePhone|Title|Birthdate|MailingStreet|MailingCity|MailingState|MailingPostalCode|MailingCountry|AccountId
                "Contact": [
                    ["002001", "", "Bob 1", "Barker 2"] + [""] * 4,
                    ["002002", "", "Sam 2", "Smith 2"] + [""] * 4,
                ],  # id|Name|StageName|CloseDate|Amount|AccountId|ContactId|record_type
                "Opportunity": [
                    [
                        "0003001",
                        "Dickenson Mobile Generators",
                        "Qualification",
                        "2020-07-18",
                        "15000.0",
                        "001003",
                        "002001",
                    ]
                ],
            }
            with mock_extract_jobs(task, extracted_records), mock_salesforce_client(
                task
            ):
                task()
            with create_engine(task.options["database_url"]).connect() as conn:
                output_accounts = list(conn.execute("select * from Account"))
                assert output_accounts[0][0:2] == (1, "Account1")
                assert output_accounts[1][0:2] == (2, "Account2")
                assert len(output_accounts) == 2
                output_opportunities = list(conn.execute("select * from Opportunity"))

                assert output_opportunities[0].AccountId == "2"
                assert output_opportunities[0].ContactId == "1"
Beispiel #20
0
    def test_create_table__already_exists(self):
        base_path = os.path.dirname(__file__)
        mapping_path = os.path.join(base_path, self.mapping_file_v1)
        db_path = os.path.join(base_path, "testdata.db")
        mock_describe_calls()
        task = _make_task(
            ExtractData,
            {
                "options": {
                    "database_url": f"sqlite:///{db_path}",
                    "mapping": mapping_path,
                }
            },
        )
        task.org_config._is_person_accounts_enabled = False

        with pytest.raises(BulkDataException):
            task()
Beispiel #21
0
    def test_run__sql(self, query_op_mock):
        base_path = os.path.dirname(__file__)
        mapping_path = os.path.join(base_path, self.mapping_file_v1)
        mock_describe_calls()

        with temporary_dir():
            task = _make_task(
                ExtractData,
                {"options": {"sql_path": "testdata.sql", "mapping": mapping_path}},
            )
            task.bulk = mock.Mock()
            task.sf = mock.Mock()
            task.org_config._is_person_accounts_enabled = False

            mock_query_households = MockBulkQueryOperation(
                sobject="Account",
                api_options={},
                context=task,
                query="SELECT Id FROM Account",
            )
            mock_query_contacts = MockBulkQueryOperation(
                sobject="Contact",
                api_options={},
                context=task,
                query="SELECT Id, FirstName, LastName, Email, AccountId FROM Contact",
            )
            mock_query_households.results = [["1"]]
            mock_query_contacts.results = [
                ["2", "First☃", "Last", "*****@*****.**", "1"]
            ]
            query_op_mock.side_effect = [mock_query_households, mock_query_contacts]

            with mock.patch(
                "cumulusci.tasks.bulkdata.extract.create_engine", wraps=create_engine
            ) as ce_mock:
                task()

            assert os.path.exists("testdata.sql")
            assert ce_mock.mock_calls[0][1][0].endswith(
                "temp_db.db"
            ), ce_mock.mock_calls[0][1][0]
            assert ce_mock.mock_calls[0][1][0].startswith(
                "sqlite:///"
            ), ce_mock.mock_calls[0][1][0]
Beispiel #22
0
    def test_run__where(self, dml_mock, query_mock):
        mock_describe_calls()
        task = _make_task(
            DeleteData,
            {"options": {
                "objects": "Contact",
                "where": "Id != null"
            }})
        query_mock.return_value.get_results.return_value = iter(
            ["001000000000000", "001000000000001"])
        query_mock.return_value.job_result = DataOperationJobResult(
            DataOperationStatus.SUCCESS, [], 2, 0)
        dml_mock.return_value.get_results.return_value = iter([
            DataOperationResult("001000000000000", True, None),
            DataOperationResult("001000000000001", True, None),
        ])
        dml_mock.return_value.job_result = DataOperationJobResult(
            DataOperationStatus.SUCCESS, [], 2, 0)
        task()
        query_mock.assert_called_once_with(
            sobject="Contact",
            api_options={},
            context=task,
            query="SELECT Id FROM Contact WHERE Id != null",
            fields=["Id"],
            api=DataApi.SMART,
        )
        query_mock.return_value.query.assert_called_once()
        query_mock.return_value.get_results.assert_called_once()

        dml_mock.assert_called_once_with(
            sobject="Contact",
            operation=DataOperationType.DELETE,
            api_options={},
            context=task,
            fields=["Id"],
            api=DataApi.SMART,
            volume=2,
        )
        dml_mock.return_value.start.assert_called_once()
        dml_mock.return_value.end.assert_called_once()
        dml_mock.return_value.load_records.assert_called_once()
        dml_mock.return_value.get_results.assert_called_once()
Beispiel #23
0
    def test_insert_dml_operation__booleans(self):
        mock_describe_calls()
        task = _make_task(
            LoadData,
            {
                "options": {
                    "database_url": "sqlite:///test.db",
                    "mapping": "mapping.yml",
                }
            },
        )
        task.project_config.project__package__api_version = "48.0"
        task._init_task()

        responses.add(
            responses.POST,
            url="https://example.com/services/data/v48.0/composite/sobjects",
            json=[{"id": "003000000000001", "success": True}],
            status=200,
        )

        recs = [["Narvaez", "True"]]
        dml_op = RestApiDmlOperation(
            sobject="Contact",
            operation=DataOperationType.INSERT,
            api_options={"batch_size": 2},
            context=task,
            fields=["LastName", "IsEmailBounced"],  # IsEmailBounced is a Boolean field.
        )

        dml_op.start()
        dml_op.load_records(iter(recs))
        dml_op.end()

        json_body = json.loads(responses.calls[1].request.body)
        assert json_body["records"] == [
            {
                "LastName": "Narvaez",
                "IsEmailBounced": True,
                "attributes": {"type": "Contact"},
            }
        ]
Beispiel #24
0
    def test_extract_memory_usage(self, step_mock):
        with TemporaryDirectory() as t:
            base_path = os.path.dirname(__file__)
            mapping_path = os.path.join(base_path, self.mapping_file_v1)
            mock_describe_calls()

            task = _make_task(
                ExtractData,
                {
                    "options": {
                        "database_url": f"sqlite:///{t}/foo.db",  # tempdir database
                        "mapping": mapping_path,
                    }
                },
            )
            task.bulk = mock.Mock()
            task.sf = mock.Mock()
            task.org_config._is_person_accounts_enabled = False

            mock_query_households = MockScalableBulkQueryOperation(
                sobject="Account",
                api_options={},
                context=task,
                query="SELECT Id FROM Account",
            )
            mock_query_contacts = MockScalableBulkQueryOperation(
                sobject="Contact",
                api_options={},
                context=task,
                query="SELECT Id, FirstName, LastName, Email, AccountId FROM Contact",
            )
            mock_query_households.results = ([str(num)] for num in range(1, 20000))
            mock_query_households.result_len = 20000
            mock_query_contacts.results = [
                ["2", "First", "Last", "*****@*****.**", "1"]
            ]
            mock_query_contacts.result_len = 1

            step_mock.side_effect = [mock_query_households, mock_query_contacts]

            with assert_max_memory_usage(15 * 10 ** 6):
                task()
    def test_validate_and_inject_mapping_enforces_fls(self):
        mock_describe_calls()
        mapping = parse_from_yaml(
            StringIO(
                "Insert Accounts:\n  sf_object: Account\n  table: Account\n  fields:\n    - Nonsense__c"
            )
        )
        org_config = DummyOrgConfig(
            {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
        )

        with pytest.raises(BulkDataException):
            validate_and_inject_mapping(
                mapping=mapping,
                org_config=org_config,
                namespace=None,
                data_operation=DataOperationType.INSERT,
                inject_namespaces=False,
                drop_missing=False,
            )
    def test_validate_and_inject_mapping_removes_namespaces(self):
        mock_describe_calls()
        # Note: History__c is a mock field added to our stored, mock describes (in JSON)
        ms = parse_from_yaml(
            StringIO(
                """Insert Accounts:
                  sf_object: Account
                  table: Account
                  fields:
                    - ns__History__c"""
            )
        )["Insert Accounts"]
        org_config = DummyOrgConfig(
            {"instance_url": "https://example.com", "access_token": "abc123"}, "test"
        )

        assert ms.validate_and_inject_namespace(
            org_config, "ns", DataOperationType.INSERT, inject_namespaces=True
        )

        assert list(ms.fields.keys()) == ["History__c"]
Beispiel #27
0
    def test_run__no_results(self, dml_mock, query_mock):
        mock_describe_calls()
        task = _make_task(DeleteData, {"options": {"objects": "Contact"}})
        query_mock.return_value.get_results.return_value = iter([])
        query_mock.return_value.job_result = DataOperationJobResult(
            DataOperationStatus.SUCCESS, [], 0, 0)

        task()

        query_mock.assert_called_once_with(
            sobject="Contact",
            fields=["Id"],
            api_options={},
            context=task,
            query="SELECT Id FROM Contact",
            api=DataApi.SMART,
        )
        query_mock.return_value.query.assert_called_once()
        query_mock.return_value.get_results.assert_not_called()

        dml_mock.assert_not_called()
Beispiel #28
0
    def test_insert_dml_operation__row_failure(self):
        mock_describe_calls()
        task = _make_task(
            LoadData,
            {
                "options": {
                    "database_url": "sqlite:///test.db",
                    "mapping": "mapping.yml",
                }
            },
        )
        task.project_config.project__package__api_version = "48.0"
        task._init_task()

        responses.add(
            responses.POST,
            url="https://example.com/services/data/v48.0/composite/sobjects",
            json=[
                {"id": "003000000000001", "success": True},
                {"id": "003000000000002", "success": True},
            ],
            status=200,
        )
        responses.add(
            responses.POST,
            url="https://example.com/services/data/v48.0/composite/sobjects",
            json=[
                {
                    "id": "003000000000003",
                    "success": False,
                    "errors": [
                        {
                            "statusCode": "VALIDATION_ERR",
                            "message": "Bad data",
                            "fields": ["FirstName"],
                        }
                    ],
                }
            ],
            status=200,
        )

        recs = [["Fred", "Narvaez"], [None, "De Vries"], ["Hiroko", "Aito"]]

        dml_op = RestApiDmlOperation(
            sobject="Contact",
            operation=DataOperationType.INSERT,
            api_options={"batch_size": 2},
            context=task,
            fields=["FirstName", "LastName"],
        )

        dml_op.start()
        dml_op.load_records(iter(recs))
        dml_op.end()

        assert dml_op.job_result == DataOperationJobResult(
            DataOperationStatus.ROW_FAILURE, [], 3, 1
        )
        assert list(dml_op.get_results()) == [
            DataOperationResult("003000000000001", True, ""),
            DataOperationResult("003000000000002", True, ""),
            DataOperationResult(
                "003000000000003", False, "VALIDATION_ERR: Bad data (FirstName)"
            ),
        ]
Beispiel #29
0
    def test_insert_dml_operation__boolean_conversion(self):
        mock_describe_calls()
        task = _make_task(
            LoadData,
            {
                "options": {
                    "database_url": "sqlite:///test.db",
                    "mapping": "mapping.yml",
                }
            },
        )
        task.project_config.project__package__api_version = "48.0"
        task._init_task()

        responses.add(
            responses.POST,
            url="https://example.com/services/data/v48.0/composite/sobjects",
            json=[
                {"id": "003000000000001", "success": True},
                {"id": "003000000000002", "success": True},
            ],
            status=200,
        )
        responses.add(
            responses.POST,
            url="https://example.com/services/data/v48.0/composite/sobjects",
            json=[{"id": "003000000000003", "success": True}],
            status=200,
        )

        recs = [
            ["Narvaez", "true"],
            ["Chalmers", "True"],
            ["De Vries", "False"],
            ["Aito", "false"],
            ["Boone", None],
            ["June", False],
            ["Zoom", True],
            ["Jewel", 0],
            ["Zule", 1],
            ["Jane", "0"],
            ["Zane", "1"],
        ]

        dml_op = RestApiDmlOperation(
            sobject="Contact",
            operation=DataOperationType.INSERT,
            context=task,
            api_options={},
            fields=["LastName", "IsEmailBounced"],
        )

        dml_op.start()
        dml_op.load_records(iter(recs))
        dml_op.end()

        assert json.loads(responses.calls[1].request.body) == {
            "allOrNone": False,
            "records": [
                {
                    "LastName": "Narvaez",
                    "IsEmailBounced": True,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "Chalmers",
                    "IsEmailBounced": True,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "De Vries",
                    "IsEmailBounced": False,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "Aito",
                    "IsEmailBounced": False,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "Boone",
                    "IsEmailBounced": False,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "June",
                    "IsEmailBounced": False,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "Zoom",
                    "IsEmailBounced": True,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "Jewel",
                    "IsEmailBounced": False,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "Zule",
                    "IsEmailBounced": True,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "Jane",
                    "IsEmailBounced": False,
                    "attributes": {"type": "Contact"},
                },
                {
                    "LastName": "Zane",
                    "IsEmailBounced": True,
                    "attributes": {"type": "Contact"},
                },
            ],
        }