From df9aab98e92d4903dcf75e238fdc496e281bb9c8 Mon Sep 17 00:00:00 2001 From: tom-gangemi <916747+tom-gangemi@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:14:43 +1100 Subject: [PATCH 1/4] Ensure that sobject type is correctly determined for SObject collections (#33) --- force-app/main/default/classes/DML.cls | 31 ++++++- force-app/main/default/classes/DML_Test.cls | 96 +++++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/force-app/main/default/classes/DML.cls b/force-app/main/default/classes/DML.cls index b91d94a..adea942 100644 --- a/force-app/main/default/classes/DML.cls +++ b/force-app/main/default/classes/DML.cls @@ -395,7 +395,8 @@ global inherited sharing class DML implements Commitable { } global Commitable toUpsert(List records, SObjectField externalIdField) { - return this.registerInDependencyOrchestrator(this.getUpsertStrategy(records.getSObjectType()).withExternalIdField(externalIdField), Records(records)); + Records dmlRecords = Records(records); + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(dmlRecords.getSObjectType()).withExternalIdField(externalIdField), dmlRecords); } global Commitable toUpsert(Records records) { @@ -1952,13 +1953,39 @@ global inherited sharing class DML implements Commitable { private List externalRelationships = new List(); private DmlRecords(List records) { - this.objectType = records.getSObjectType(); + this.objectType = this.resolveObjectType(records); for (SObject record : records) { this.records.add(new DmlRecord(record)); } } + private SObjectType resolveObjectType(List records) { + if(records == null) { + return null; + } + + SObjectType resolvedType = null; + + for (SObject record : records) { + if (record == null) { + continue; + } + + SObjectType currentType = record.getSObjectType(); + if (resolvedType == null) { + resolvedType = currentType; + continue; + } + + if (resolvedType != currentType) { + throw new DmlException('Mixed SObject types in a single List operation are not supported.'); + } + } + + return resolvedType; + } + private DmlRecords(Iterable recordIds) { this.setObjectTypeBasedOnIds(recordIds); diff --git a/force-app/main/default/classes/DML_Test.cls b/force-app/main/default/classes/DML_Test.cls index 1c9066d..b1f957a 100644 --- a/force-app/main/default/classes/DML_Test.cls +++ b/force-app/main/default/classes/DML_Test.cls @@ -1146,6 +1146,102 @@ private class DML_Test { Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); } + @IsTest + static void toUpsertGenericSObjectListWithExternalIdFieldWithMocking() { + // Setup + Account account1 = getAccount(1); + List genericRecords = new List{ account1 }; + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toUpsert(genericRecords, Account.Id).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + } + + @IsTest + static void listSObjectOverloadsResolveRuntimeTypeAcrossOperationsWithMocking() { + // Setup + Account insertAccount = getAccount(101); + List insertRecords = new List{ insertAccount }; + DML.mock('genericInsertMockId').allInserts(); + + Account updateAccount = getAccount(102); + updateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List updateRecords = new List{ updateAccount }; + DML.mock('genericUpdateMockId').allUpdates(); + + Account deleteAccount = getAccount(103); + deleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List deleteRecords = new List{ deleteAccount }; + DML.mock('genericDeleteMockId').allDeletes(); + + Account hardDeleteAccount = getAccount(104); + hardDeleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List hardDeleteRecords = new List{ hardDeleteAccount }; + DML.mock('genericHardDeleteMockId').allDeletes(); + + Account undeleteAccount = getAccount(105); + undeleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List undeleteRecords = new List{ undeleteAccount }; + DML.mock('genericUndeleteMockId').allUndeletes(); + + // Test + Test.startTest(); + new DML().toInsert(insertRecords).identifier('genericInsertMockId').commitWork(); + new DML().toUpdate(updateRecords).identifier('genericUpdateMockId').commitWork(); + new DML().toDelete(deleteRecords).identifier('genericDeleteMockId').commitWork(); + new DML().toHardDelete(hardDeleteRecords).identifier('genericHardDeleteMockId').commitWork(); + new DML().toUndelete(undeleteRecords).identifier('genericUndeleteMockId').commitWork(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, DML.retrieveResultFor('genericInsertMockId').insertsOf(Account.SObjectType).recordResults().size(), 'Insert result should be keyed by Account SObjectType.'); + Assert.areEqual(1, DML.retrieveResultFor('genericUpdateMockId').updatesOf(Account.SObjectType).recordResults().size(), 'Update result should be keyed by Account SObjectType.'); + Assert.areEqual(1, DML.retrieveResultFor('genericDeleteMockId').deletesOf(Account.SObjectType).recordResults().size(), 'Delete result should be keyed by Account SObjectType.'); + Assert.areEqual(1, DML.retrieveResultFor('genericHardDeleteMockId').deletesOf(Account.SObjectType).recordResults().size(), 'Hard delete result should be keyed by Account SObjectType.'); + Assert.areEqual(1, DML.retrieveResultFor('genericUndeleteMockId').undeletesOf(Account.SObjectType).recordResults().size(), 'Undelete result should be keyed by Account SObjectType.'); + } + + @IsTest + static void listSObjectOverloadsThrowWhenMixedTypesProvided() { + // Setup + List mixedRecords = new List{ + getAccount(1), + getContact(1) + }; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpsert(mixedRecords).identifier('mixedSObjectTypeMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown for mixed SObject types in one list operation.'); + Assert.isTrue(expectedException.getMessage().contains('Mixed SObject types'), 'Expected mixed SObject type validation message.'); + } + @IsTest static void toUpsertMultipleRecordsWithMocking() { // Setup From d4691bfb48bbe65f20eb0e5e39d2e893b3b664d8 Mon Sep 17 00:00:00 2001 From: Piotr PG Gajek Date: Tue, 12 May 2026 21:32:56 +0200 Subject: [PATCH 2/4] package directory --- force-app/main/default/classes/DML.cls | 4403 +++++----- force-app/main/default/classes/DML_Test.cls | 7343 ++++++++-------- .../main/default/classes/DML_Full_Test.cls | 7703 ---------------- package/main/default/classes/DML.cls | 2366 +++++ .../main/default/classes/DML.cls-meta.xml | 0 .../main/default/classes/DML_Full_Test.cls | 7724 +++++++++++++++++ .../classes/DML_Full_Test.cls-meta.xml | 5 + sfdx-project.json | 8 +- 8 files changed, 15960 insertions(+), 13592 deletions(-) delete mode 100644 internal/main/default/classes/DML_Full_Test.cls create mode 100644 package/main/default/classes/DML.cls rename internal/main/default/classes/DML_Full_Test.cls-meta.xml => package/main/default/classes/DML.cls-meta.xml (100%) create mode 100644 package/main/default/classes/DML_Full_Test.cls create mode 100644 package/main/default/classes/DML_Full_Test.cls-meta.xml diff --git a/force-app/main/default/classes/DML.cls b/force-app/main/default/classes/DML.cls index adea942..8e3c2fe 100644 --- a/force-app/main/default/classes/DML.cls +++ b/force-app/main/default/classes/DML.cls @@ -2,7 +2,7 @@ * Copyright (c) 2026 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev) * Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/dml-lib/blob/main/LICENSE) * - * v3.0.0 + * v3.1.0 * * PMD False Positives: * - MethodNamingConventions: Some methods are uppercase to indicate that they are "constructors" of other internal classes @@ -21,2347 +21,2346 @@ * - AvoidGlobalModifier - DML has package version **/ @SuppressWarnings( - 'PMD.MethodNamingConventions,PMD.ApexDoc,PMD.CyclomaticComplexity,PMD.CognitiveComplexity,PMD.ExcessivePublicCount,PMD.FieldDeclarationsShouldBeAtStart,PMD.AvoidDebugStatements,PMD.OperationWithLimitsInLoop,PMD.ApexCRUDViolation,PMD.ExcessiveClassLength,PMD.NcssTypeCount,PMD.PropertyNamingConventions,PMD.FieldNamingConventions,PMD.AvoidGlobalModifier' + 'PMD.MethodNamingConventions,PMD.ApexDoc,PMD.CyclomaticComplexity,PMD.CognitiveComplexity,PMD.ExcessivePublicCount,PMD.FieldDeclarationsShouldBeAtStart,PMD.AvoidDebugStatements,PMD.OperationWithLimitsInLoop,PMD.ApexCRUDViolation,PMD.ExcessiveClassLength,PMD.NcssTypeCount,PMD.PropertyNamingConventions,PMD.FieldNamingConventions,PMD.AvoidGlobalModifier' ) -global inherited sharing class DML implements Commitable { - global static Commitable Shared { - get { - if (Shared == null) { - Shared = new DML(); - } - return Shared; - } - private set; - } - - global static Record Record(SObject record) { - return new DmlRecord(record); - } - - global static Record Record(Id recordId) { - return new DmlRecord(recordId); - } - - global static Records Records(List records) { - return new DmlRecords(records); - } - - global static Records Records(Iterable recordIds) { - return new DmlRecords(recordIds); - } - - global interface Commitable { - // Insert - Commitable toInsert(SObject record); - Commitable toInsert(DML.Record record); - Commitable toInsert(List records); - Commitable toInsert(DML.Records records); - // Update - Commitable toUpdate(SObject record); - Commitable toUpdate(DML.Record record); - Commitable toUpdate(List records); - Commitable toUpdate(DML.Records records); - // Upsert - Commitable toUpsert(SObject record); - Commitable toUpsert(SObject record, SObjectField externalIdField); - Commitable toUpsert(DML.Record record); - Commitable toUpsert(List records); - Commitable toUpsert(List records, SObjectField externalIdField); - Commitable toUpsert(DML.Records records); - // Delete - Commitable toDelete(Id recordId); - Commitable toDelete(SObject record); - Commitable toDelete(Iterable recordIds); - Commitable toDelete(List records); - // Hard Delete - Commitable toHardDelete(Id recordId); - Commitable toHardDelete(SObject record); - Commitable toHardDelete(Iterable recordIds); - Commitable toHardDelete(List records); - // Undelete - Commitable toUndelete(Id recordId); - Commitable toUndelete(SObject record); - Commitable toUndelete(Iterable recordIds); - Commitable toUndelete(List records); - // Merge - Commitable toMerge(SObject mergeToRecord, SObject duplicatedRecord); - Commitable toMerge(SObject mergeToRecord, List duplicateRecords); - Commitable toMerge(SObject mergeToRecord, Id duplicatedRecordId); - Commitable toMerge(SObject mergeToRecord, Iterable duplicatedRecordIds); - // Platform Event - Commitable toPublish(SObject record); - Commitable toPublish(List records); - // Immediate Insert - OperationResult insertImmediately(SObject record); - OperationResult insertImmediately(DML.Record record); - OperationResult insertImmediately(List records); - OperationResult insertImmediately(DML.Records records); - // Immediate Update - OperationResult updateImmediately(SObject record); - OperationResult updateImmediately(DML.Record record); - OperationResult updateImmediately(List records); - OperationResult updateImmediately(DML.Records records); - // Immediate Upsert - OperationResult upsertImmediately(SObject record); - OperationResult upsertImmediately(DML.Record record); - OperationResult upsertImmediately(List records); - OperationResult upsertImmediately(DML.Records records); - // Immediate Delete - OperationResult deleteImmediately(Id recordId); - OperationResult deleteImmediately(SObject record); - OperationResult deleteImmediately(Iterable recordIds); - OperationResult deleteImmediately(List records); - // Immediate Undelete - OperationResult undeleteImmediately(Id recordId); - OperationResult undeleteImmediately(SObject record); - OperationResult undeleteImmediately(Iterable recordIds); - OperationResult undeleteImmediately(List records); - // Immediate Publish - OperationResult publishImmediately(SObject record); - OperationResult publishImmediately(List records); - // Mocking - Commitable identifier(String dmlIdentifier); // used for mocking and tracking results - // Debug - void preview(); - // Field Level Security - Commitable userMode(); - Commitable systemMode(); - // Sharing Mode - Commitable withSharing(); - Commitable withoutSharing(); - // Other configs - Commitable allowPartialSuccess(); - Commitable skipDuplicateRules(); - Commitable options(Database.DmlOptions options); - Commitable discardWork(); - Commitable commitHook(DML.Hook callback); - Commitable combineOnDuplicate(); - // Save - Result dryRun(); - Result commitWork(); - Result commitTransaction(); // make a savepoint and rollback on exception - } - - global interface Record { - Record with(SObjectField field, Object value); - Record withRelationship(SObjectField targetField, SObject relatedRecord); - Record withRelationship(SObjectField targetField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId); - - // for internal use only - EnhancedRecord get(); - SObjectType getSObjectType(); - } - - global interface Records { - Records with(SObjectField field, Object value); - Records withRelationship(SObjectField targetField, SObject relatedRecord); - Records withRelationship(SObjectField targetField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId); - - // for internal use only - List get(); - SObjectType getSObjectType(); - } - - global interface Result { - List all(); - // Per Operation - List inserts(); - List updates(); - List upserts(); - List deletes(); - List undeletes(); - List merges(); - List events(); - // Per Object OperationType - OperationResult insertsOf(SObjectType objectType); - OperationResult updatesOf(SObjectType objectType); - OperationResult upsertsOf(SObjectType objectType); - OperationResult deletesOf(SObjectType objectType); - OperationResult undeletesOf(SObjectType objectType); - OperationResult mergesOf(SObjectType objectType); - OperationResult eventsOf(SObjectType objectType); - } - - global interface OperationResult { - // Metadata - DML.OperationType operationType(); - SObjectType objectType(); - Boolean hasFailures(); - // Errors - List errors(); - // Records - List records(); - List successes(); - List failures(); - // Details - List recordResults(); - } - - global interface RecordResult { - Id id(); - SObject record(); - Boolean isSuccess(); - List errors(); - } - - global interface Error { - String message(); - System.StatusCode statusCode(); - List fields(); - } - - // Mocking - - global static DML.Mockable mock(String dmlIdentifier) { - if (!dmlIdentifierToMock.containsKey(dmlIdentifier)) { - dmlIdentifierToMock.put(dmlIdentifier, new DmlMock()); - } - return dmlIdentifierToMock.get(dmlIdentifier); - } - - global static DML.Result retrieveResultFor(String dmlIdentifier) { - if (!dmlIdentifierToResult.containsKey(dmlIdentifier)) { - throw new DmlException('No result found for dml identifier: ' + dmlIdentifier); - } - return dmlIdentifierToResult.get(dmlIdentifier); - } - - global interface Mockable { - Mockable allDmls(); - // Per Operation - Mockable allInserts(); - Mockable allUpdates(); - Mockable allUpserts(); - Mockable allDeletes(); - Mockable allUndeletes(); - Mockable allMerges(); - Mockable allPublishes(); - // Per Object OperationType - Mockable insertsFor(SObjectType objectType); - Mockable updatesFor(SObjectType objectType); - Mockable upsertsFor(SObjectType objectType); - Mockable deletesFor(SObjectType objectType); - Mockable undeletesFor(SObjectType objectType); - Mockable mergesFor(SObjectType objectType); - Mockable publishesFor(SObjectType objectType); - // Errors - Mockable exceptionOnInserts(); - Mockable exceptionOnUpdates(); - Mockable exceptionOnUpserts(); - Mockable exceptionOnDeletes(); - Mockable exceptionOnUndeletes(); - Mockable exceptionOnMerges(); - Mockable exceptionOnPublishes(); - // Per Operation Type Per Object Type - Mockable exceptionOnInsertsFor(SObjectType objectType); - Mockable exceptionOnUpdatesFor(SObjectType objectType); - Mockable exceptionOnUpsertsFor(SObjectType objectType); - Mockable exceptionOnDeletesFor(SObjectType objectType); - Mockable exceptionOnUndeletesFor(SObjectType objectType); - Mockable exceptionOnMergesFor(SObjectType objectType); - Mockable exceptionOnPublishesFor(SObjectType objectType); - } - - // Hooks - - global interface Hook { - void before(); - void after(Result result); - } - - // Implementation - - global enum OperationType { - INSERT_DML, - UPSERT_DML, - UPDATE_DML, - MERGE_DML, - DELETE_DML, - UNDELETE_DML, - PUBLISH_DML - } - - private Configuration configuration; - private StrategiesStorage strategiesStorage; - private DependencyOrchestrator dependencyOrchestrator; - private LinearOrchestrator linearOrchestrator; - - private Hook hook; - - @TestVisible - private static final RandomIdGenerator randomIdGenerator = new RandomIdGenerator(); - private static final Map dmlIdentifierToResult = new Map(); - private static final Map dmlIdentifierToMock = new Map(); - - global DML() { - this.configuration = new Configuration(); - this.strategiesStorage = new StrategiesStorage(); - this.dependencyOrchestrator = new DependencyOrchestrator(); - this.linearOrchestrator = new LinearOrchestrator(this.configuration); - } - - // Insert - - global Commitable toInsert(SObject record) { - return this.toInsert(Record(record)); - } - - global Commitable toInsert(Record record) { - return this.registerInDependencyOrchestrator(this.getInsertStrategy(record.getSObjectType()), record); - } - - global Commitable toInsert(List records) { - return this.toInsert(Records(records)); - } - - global Commitable toInsert(Records records) { - return this.registerInDependencyOrchestrator(this.getInsertStrategy(records.getSObjectType()), records); - } - - global OperationResult insertImmediately(SObject record) { - return this.insertImmediately(Record(record)); - } - - global OperationResult insertImmediately(Record record) { - return this.executeImmediately(this.getInsertStrategy(record.getSObjectType()), record); - } - - global OperationResult insertImmediately(List records) { - return this.insertImmediately(Records(records)); - } - - global OperationResult insertImmediately(Records records) { - return this.executeImmediately(this.getInsertStrategy(records.getSObjectType()), records); - } - - private InsertStrategy getInsertStrategy(SObjectType objectType) { - return new InsertStrategy(objectType, this.configuration); - } - - // Update - - global Commitable toUpdate(SObject record) { - return this.toUpdate(Record(record)); - } - - global Commitable toUpdate(Record record) { - return this.registerInLinearOrchestrator(this.getUpdateStrategy(record.getSObjectType()), record); - } - - global Commitable toUpdate(List records) { - return this.toUpdate(Records(records)); - } - - global Commitable toUpdate(Records records) { - return this.registerInLinearOrchestrator(this.getUpdateStrategy(records.getSObjectType()), records); - } - - global OperationResult updateImmediately(SObject record) { - return this.updateImmediately(Record(record)); - } +public inherited sharing class DML implements Commitable { + public static Commitable Shared { + get { + if (Shared == null) { + Shared = new DML(); + } + return Shared; + } + private set; + } + + public static Record Record(SObject record) { + return new DmlRecord(record); + } + + public static Record Record(Id recordId) { + return new DmlRecord(recordId); + } + + public static Records Records(List records) { + return new DmlRecords(records); + } + + public static Records Records(Iterable recordIds) { + return new DmlRecords(recordIds); + } + + public interface Commitable { + // Insert + Commitable toInsert(SObject record); + Commitable toInsert(DML.Record record); + Commitable toInsert(List records); + Commitable toInsert(DML.Records records); + // Update + Commitable toUpdate(SObject record); + Commitable toUpdate(DML.Record record); + Commitable toUpdate(List records); + Commitable toUpdate(DML.Records records); + // Upsert + Commitable toUpsert(SObject record); + Commitable toUpsert(SObject record, SObjectField externalIdField); + Commitable toUpsert(DML.Record record); + Commitable toUpsert(List records); + Commitable toUpsert(List records, SObjectField externalIdField); + Commitable toUpsert(DML.Records records); + // Delete + Commitable toDelete(Id recordId); + Commitable toDelete(SObject record); + Commitable toDelete(Iterable recordIds); + Commitable toDelete(List records); + // Hard Delete + Commitable toHardDelete(Id recordId); + Commitable toHardDelete(SObject record); + Commitable toHardDelete(Iterable recordIds); + Commitable toHardDelete(List records); + // Undelete + Commitable toUndelete(Id recordId); + Commitable toUndelete(SObject record); + Commitable toUndelete(Iterable recordIds); + Commitable toUndelete(List records); + // Merge + Commitable toMerge(SObject mergeToRecord, SObject duplicatedRecord); + Commitable toMerge(SObject mergeToRecord, List duplicateRecords); + Commitable toMerge(SObject mergeToRecord, Id duplicatedRecordId); + Commitable toMerge(SObject mergeToRecord, Iterable duplicatedRecordIds); + // Platform Event + Commitable toPublish(SObject record); + Commitable toPublish(List records); + // Immediate Insert + OperationResult insertImmediately(SObject record); + OperationResult insertImmediately(DML.Record record); + OperationResult insertImmediately(List records); + OperationResult insertImmediately(DML.Records records); + // Immediate Update + OperationResult updateImmediately(SObject record); + OperationResult updateImmediately(DML.Record record); + OperationResult updateImmediately(List records); + OperationResult updateImmediately(DML.Records records); + // Immediate Upsert + OperationResult upsertImmediately(SObject record); + OperationResult upsertImmediately(DML.Record record); + OperationResult upsertImmediately(List records); + OperationResult upsertImmediately(DML.Records records); + // Immediate Delete + OperationResult deleteImmediately(Id recordId); + OperationResult deleteImmediately(SObject record); + OperationResult deleteImmediately(Iterable recordIds); + OperationResult deleteImmediately(List records); + // Immediate Undelete + OperationResult undeleteImmediately(Id recordId); + OperationResult undeleteImmediately(SObject record); + OperationResult undeleteImmediately(Iterable recordIds); + OperationResult undeleteImmediately(List records); + // Immediate Publish + OperationResult publishImmediately(SObject record); + OperationResult publishImmediately(List records); + // Mocking + Commitable identifier(String dmlIdentifier); // used for mocking and tracking results + // Debug + void preview(); + // Field Level Security + Commitable userMode(); + Commitable systemMode(); + // Sharing Mode + Commitable withSharing(); + Commitable withoutSharing(); + // Other configs + Commitable allowPartialSuccess(); + Commitable skipDuplicateRules(); + Commitable options(Database.DmlOptions options); + Commitable discardWork(); + Commitable commitHook(DML.Hook callback); + Commitable combineOnDuplicate(); + // Save + Result dryRun(); + Result commitWork(); + Result commitTransaction(); // make a savepoint and rollback on exception + } + + public interface Record { + Record with(SObjectField field, Object value); + Record withRelationship(SObjectField targetField, SObject relatedRecord); + Record withRelationship(SObjectField targetField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId); + + // for internal use only + EnhancedRecord get(); + SObjectType getSObjectType(); + } + + public interface Records { + Records with(SObjectField field, Object value); + Records withRelationship(SObjectField targetField, SObject relatedRecord); + Records withRelationship(SObjectField targetField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId); + + // for internal use only + List get(); + SObjectType getSObjectType(); + } + + public interface Result { + List all(); + // Per Operation + List inserts(); + List updates(); + List upserts(); + List deletes(); + List undeletes(); + List merges(); + List events(); + // Per Object OperationType + OperationResult insertsOf(SObjectType objectType); + OperationResult updatesOf(SObjectType objectType); + OperationResult upsertsOf(SObjectType objectType); + OperationResult deletesOf(SObjectType objectType); + OperationResult undeletesOf(SObjectType objectType); + OperationResult mergesOf(SObjectType objectType); + OperationResult eventsOf(SObjectType objectType); + } + + public interface OperationResult { + // Metadata + DML.OperationType operationType(); + SObjectType objectType(); + Boolean hasFailures(); + // Errors + List errors(); + // Records + List records(); + List successes(); + List failures(); + // Details + List recordResults(); + } + + public interface RecordResult { + Id id(); + SObject record(); + Boolean isSuccess(); + List errors(); + } + + public interface Error { + String message(); + System.StatusCode statusCode(); + List fields(); + } + + // Mocking + + public static DML.Mockable mock(String dmlIdentifier) { + if (!dmlIdentifierToMock.containsKey(dmlIdentifier)) { + dmlIdentifierToMock.put(dmlIdentifier, new DmlMock()); + } + return dmlIdentifierToMock.get(dmlIdentifier); + } + + public static DML.Result retrieveResultFor(String dmlIdentifier) { + if (!dmlIdentifierToResult.containsKey(dmlIdentifier)) { + throw new DmlException('No result found for dml identifier: ' + dmlIdentifier); + } + return dmlIdentifierToResult.get(dmlIdentifier); + } + + public interface Mockable { + Mockable allDmls(); + // Per Operation + Mockable allInserts(); + Mockable allUpdates(); + Mockable allUpserts(); + Mockable allDeletes(); + Mockable allUndeletes(); + Mockable allMerges(); + Mockable allPublishes(); + // Per Object OperationType + Mockable insertsFor(SObjectType objectType); + Mockable updatesFor(SObjectType objectType); + Mockable upsertsFor(SObjectType objectType); + Mockable deletesFor(SObjectType objectType); + Mockable undeletesFor(SObjectType objectType); + Mockable mergesFor(SObjectType objectType); + Mockable publishesFor(SObjectType objectType); + // Errors + Mockable exceptionOnInserts(); + Mockable exceptionOnUpdates(); + Mockable exceptionOnUpserts(); + Mockable exceptionOnDeletes(); + Mockable exceptionOnUndeletes(); + Mockable exceptionOnMerges(); + Mockable exceptionOnPublishes(); + // Per Operation Type Per Object Type + Mockable exceptionOnInsertsFor(SObjectType objectType); + Mockable exceptionOnUpdatesFor(SObjectType objectType); + Mockable exceptionOnUpsertsFor(SObjectType objectType); + Mockable exceptionOnDeletesFor(SObjectType objectType); + Mockable exceptionOnUndeletesFor(SObjectType objectType); + Mockable exceptionOnMergesFor(SObjectType objectType); + Mockable exceptionOnPublishesFor(SObjectType objectType); + } + + // Hooks + + public interface Hook { + void before(); + void after(Result result); + } + + // Implementation + + public enum OperationType { + INSERT_DML, + UPSERT_DML, + UPDATE_DML, + MERGE_DML, + DELETE_DML, + UNDELETE_DML, + PUBLISH_DML + } + + private Configuration configuration; + private StrategiesStorage strategiesStorage; + private DependencyOrchestrator dependencyOrchestrator; + private LinearOrchestrator linearOrchestrator; + + private Hook hook; + + @TestVisible + private static final RandomIdGenerator randomIdGenerator = new RandomIdGenerator(); + private static final Map dmlIdentifierToResult = new Map(); + private static final Map dmlIdentifierToMock = new Map(); + + public DML() { + this.configuration = new Configuration(); + this.strategiesStorage = new StrategiesStorage(); + this.dependencyOrchestrator = new DependencyOrchestrator(); + this.linearOrchestrator = new LinearOrchestrator(this.configuration); + } + + // Insert + + public Commitable toInsert(SObject record) { + return this.toInsert(Record(record)); + } + + public Commitable toInsert(Record record) { + return this.registerInDependencyOrchestrator(this.getInsertStrategy(record.getSObjectType()), record); + } + + public Commitable toInsert(List records) { + return this.toInsert(Records(records)); + } + + public Commitable toInsert(Records records) { + return this.registerInDependencyOrchestrator(this.getInsertStrategy(records.getSObjectType()), records); + } + + public OperationResult insertImmediately(SObject record) { + return this.insertImmediately(Record(record)); + } + + public OperationResult insertImmediately(Record record) { + return this.executeImmediately(this.getInsertStrategy(record.getSObjectType()), record); + } + + public OperationResult insertImmediately(List records) { + return this.insertImmediately(Records(records)); + } + + public OperationResult insertImmediately(Records records) { + return this.executeImmediately(this.getInsertStrategy(records.getSObjectType()), records); + } + + private InsertStrategy getInsertStrategy(SObjectType objectType) { + return new InsertStrategy(objectType, this.configuration); + } + + // Update + + public Commitable toUpdate(SObject record) { + return this.toUpdate(Record(record)); + } + + public Commitable toUpdate(Record record) { + return this.registerInLinearOrchestrator(this.getUpdateStrategy(record.getSObjectType()), record); + } + + public Commitable toUpdate(List records) { + return this.toUpdate(Records(records)); + } + + public Commitable toUpdate(Records records) { + return this.registerInLinearOrchestrator(this.getUpdateStrategy(records.getSObjectType()), records); + } + + public OperationResult updateImmediately(SObject record) { + return this.updateImmediately(Record(record)); + } - global OperationResult updateImmediately(Record record) { - return this.executeImmediately(this.getUpdateStrategy(record.getSObjectType()), record); - } + public OperationResult updateImmediately(Record record) { + return this.executeImmediately(this.getUpdateStrategy(record.getSObjectType()), record); + } - global OperationResult updateImmediately(List records) { - return this.updateImmediately(Records(records)); - } + public OperationResult updateImmediately(List records) { + return this.updateImmediately(Records(records)); + } - global OperationResult updateImmediately(Records records) { - return this.executeImmediately(this.getUpdateStrategy(records.getSObjectType()), records); - } - - private UpdateStrategy getUpdateStrategy(SObjectType objectType) { - return new UpdateStrategy(objectType, this.configuration); - } + public OperationResult updateImmediately(Records records) { + return this.executeImmediately(this.getUpdateStrategy(records.getSObjectType()), records); + } + + private UpdateStrategy getUpdateStrategy(SObjectType objectType) { + return new UpdateStrategy(objectType, this.configuration); + } - // Upsert + // Upsert - global Commitable toUpsert(SObject record) { - return this.toUpsert(Record(record)); - } + public Commitable toUpsert(SObject record) { + return this.toUpsert(Record(record)); + } - global Commitable toUpsert(SObject record, SObjectField externalIdField) { - return this.registerInDependencyOrchestrator(this.getUpsertStrategy(record.getSObjectType()).withExternalIdField(externalIdField), Record(record)); - } + public Commitable toUpsert(SObject record, SObjectField externalIdField) { + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(record.getSObjectType()).withExternalIdField(externalIdField), Record(record)); + } - global Commitable toUpsert(Record record) { - return this.registerInDependencyOrchestrator(this.getUpsertStrategy(record.getSObjectType()), record); - } + public Commitable toUpsert(Record record) { + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(record.getSObjectType()), record); + } - global Commitable toUpsert(List records) { - return this.toUpsert(Records(records)); - } + public Commitable toUpsert(List records) { + return this.toUpsert(Records(records)); + } - global Commitable toUpsert(List records, SObjectField externalIdField) { - Records dmlRecords = Records(records); - return this.registerInDependencyOrchestrator(this.getUpsertStrategy(dmlRecords.getSObjectType()).withExternalIdField(externalIdField), dmlRecords); - } + public Commitable toUpsert(List records, SObjectField externalIdField) { + Records dmlRecords = Records(records); + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(dmlRecords.getSObjectType()).withExternalIdField(externalIdField), dmlRecords); + } - global Commitable toUpsert(Records records) { - return this.registerInDependencyOrchestrator(this.getUpsertStrategy(records.getSObjectType()), records); - } + public Commitable toUpsert(Records records) { + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(records.getSObjectType()), records); + } - global OperationResult upsertImmediately(SObject record) { - return this.upsertImmediately(Record(record)); - } + public OperationResult upsertImmediately(SObject record) { + return this.upsertImmediately(Record(record)); + } - global OperationResult upsertImmediately(Record record) { - return this.executeImmediately(this.getUpsertStrategy(record.getSObjectType()), record); - } + public OperationResult upsertImmediately(Record record) { + return this.executeImmediately(this.getUpsertStrategy(record.getSObjectType()), record); + } - global OperationResult upsertImmediately(List records) { - return this.upsertImmediately(Records(records)); - } + public OperationResult upsertImmediately(List records) { + return this.upsertImmediately(Records(records)); + } - global OperationResult upsertImmediately(Records records) { - return this.executeImmediately(this.getUpsertStrategy(records.getSObjectType()), records); - } + public OperationResult upsertImmediately(Records records) { + return this.executeImmediately(this.getUpsertStrategy(records.getSObjectType()), records); + } - private UpsertStrategy getUpsertStrategy(SObjectType objectType) { - return new UpsertStrategy(objectType, this.configuration); - } + private UpsertStrategy getUpsertStrategy(SObjectType objectType) { + return new UpsertStrategy(objectType, this.configuration); + } - // Delete + // Delete - global Commitable toDelete(Id recordId) { - return this.toDelete(Record(recordId)); - } + public Commitable toDelete(Id recordId) { + return this.toDelete(Record(recordId)); + } - global Commitable toDelete(SObject record) { - return this.toDelete(Record(record)); - } + public Commitable toDelete(SObject record) { + return this.toDelete(Record(record)); + } - private Commitable toDelete(Record record) { - return this.registerInLinearOrchestrator(this.getDeleteStrategy(record.getSObjectType()), record); - } + private Commitable toDelete(Record record) { + return this.registerInLinearOrchestrator(this.getDeleteStrategy(record.getSObjectType()), record); + } - global Commitable toDelete(Iterable recordIds) { - return this.toDelete(Records(recordIds)); - } + public Commitable toDelete(Iterable recordIds) { + return this.toDelete(Records(recordIds)); + } - global Commitable toDelete(List records) { - return this.toDelete(Records(records)); - } + public Commitable toDelete(List records) { + return this.toDelete(Records(records)); + } - private Commitable toDelete(Records records) { - return this.registerInLinearOrchestrator(this.getDeleteStrategy(records.getSObjectType()), records); - } + private Commitable toDelete(Records records) { + return this.registerInLinearOrchestrator(this.getDeleteStrategy(records.getSObjectType()), records); + } - global OperationResult deleteImmediately(Id recordId) { - return this.deleteImmediately(Record(recordId)); - } + public OperationResult deleteImmediately(Id recordId) { + return this.deleteImmediately(Record(recordId)); + } - global OperationResult deleteImmediately(SObject record) { - return this.deleteImmediately(Record(record)); - } + public OperationResult deleteImmediately(SObject record) { + return this.deleteImmediately(Record(record)); + } - private OperationResult deleteImmediately(Record record) { - return this.executeImmediately(this.getDeleteStrategy(record.getSObjectType()), record); - } + private OperationResult deleteImmediately(Record record) { + return this.executeImmediately(this.getDeleteStrategy(record.getSObjectType()), record); + } - global OperationResult deleteImmediately(Iterable recordIds) { - return this.deleteImmediately(Records(recordIds)); - } + public OperationResult deleteImmediately(Iterable recordIds) { + return this.deleteImmediately(Records(recordIds)); + } - global OperationResult deleteImmediately(List records) { - return this.deleteImmediately(Records(records)); - } + public OperationResult deleteImmediately(List records) { + return this.deleteImmediately(Records(records)); + } - private OperationResult deleteImmediately(Records records) { - return this.executeImmediately(this.getDeleteStrategy(records.getSObjectType()), records); - } + private OperationResult deleteImmediately(Records records) { + return this.executeImmediately(this.getDeleteStrategy(records.getSObjectType()), records); + } - private DeleteStrategy getDeleteStrategy(SObjectType objectType) { - return new DeleteStrategy(objectType, this.configuration); - } + private DeleteStrategy getDeleteStrategy(SObjectType objectType) { + return new DeleteStrategy(objectType, this.configuration); + } - // Hard Delete + // Hard Delete - global Commitable toHardDelete(Id recordId) { - Record dmlRecord = Record(recordId); - return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecord.getSObjectType()).withHardDelete(), dmlRecord); - } + public Commitable toHardDelete(Id recordId) { + Record dmlRecord = Record(recordId); + return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecord.getSObjectType()).withHardDelete(), dmlRecord); + } - global Commitable toHardDelete(SObject record) { - Record dmlRecord = Record(record); - return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecord.getSObjectType()).withHardDelete(), dmlRecord); - } + public Commitable toHardDelete(SObject record) { + Record dmlRecord = Record(record); + return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecord.getSObjectType()).withHardDelete(), dmlRecord); + } - global Commitable toHardDelete(Iterable recordIds) { - Records dmlRecords = Records(recordIds); - return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecords.getSObjectType()).withHardDelete(), dmlRecords); - } + public Commitable toHardDelete(Iterable recordIds) { + Records dmlRecords = Records(recordIds); + return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecords.getSObjectType()).withHardDelete(), dmlRecords); + } - global Commitable toHardDelete(List records) { - Records dmlRecords = Records(records); - return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecords.getSObjectType()).withHardDelete(), dmlRecords); - } + public Commitable toHardDelete(List records) { + Records dmlRecords = Records(records); + return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecords.getSObjectType()).withHardDelete(), dmlRecords); + } - // Undelete + // Undelete - global Commitable toUndelete(Id recordId) { - return this.toUndelete(Record(recordId)); - } + public Commitable toUndelete(Id recordId) { + return this.toUndelete(Record(recordId)); + } - global Commitable toUndelete(SObject record) { - return this.toUndelete(Record(record)); - } + public Commitable toUndelete(SObject record) { + return this.toUndelete(Record(record)); + } - private Commitable toUndelete(Record record) { - return this.registerInLinearOrchestrator(this.getUndeleteStrategy(record.getSObjectType()), record); - } + private Commitable toUndelete(Record record) { + return this.registerInLinearOrchestrator(this.getUndeleteStrategy(record.getSObjectType()), record); + } - global Commitable toUndelete(Iterable recordIds) { - return this.toUndelete(Records(recordIds)); - } + public Commitable toUndelete(Iterable recordIds) { + return this.toUndelete(Records(recordIds)); + } - global Commitable toUndelete(List records) { - return this.toUndelete(Records(records)); - } - - private Commitable toUndelete(Records records) { - return this.registerInLinearOrchestrator(this.getUndeleteStrategy(records.getSObjectType()), records); - } - - global OperationResult undeleteImmediately(Id recordId) { - return this.undeleteImmediately(Record(recordId)); - } - - global OperationResult undeleteImmediately(SObject record) { - return this.undeleteImmediately(Record(record)); - } - - private OperationResult undeleteImmediately(Record record) { - return this.executeImmediately(this.getUndeleteStrategy(record.getSObjectType()), record); - } - - global OperationResult undeleteImmediately(Iterable recordIds) { - return this.undeleteImmediately(Records(recordIds)); - } - - global OperationResult undeleteImmediately(List records) { - return this.undeleteImmediately(Records(records)); - } - - private OperationResult undeleteImmediately(Records records) { - return this.executeImmediately(this.getUndeleteStrategy(records.getSObjectType()), records); - } - - private UndeleteStrategy getUndeleteStrategy(SObjectType objectType) { - return new UndeleteStrategy(objectType, this.configuration); - } - - // Merge - - global Commitable toMerge(SObject mergeToRecord, SObject duplicatedRecord) { - Record dmlMergeToRecord = Record(mergeToRecord); - return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Record(duplicatedRecord)); - } - - global Commitable toMerge(SObject mergeToRecord, Id duplicatedRecordId) { - Record dmlMergeToRecord = Record(mergeToRecord); - return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Record(duplicatedRecordId)); - } - - private Commitable toMerge(MergeStrategy mergeStrategy, Record duplicatedRecord) { - return this.registerInLinearOrchestrator(mergeStrategy, duplicatedRecord); - } - - global Commitable toMerge(SObject mergeToRecord, List duplicateRecords) { - Record dmlMergeToRecord = Record(mergeToRecord); - return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Records(duplicateRecords)); - } - - global Commitable toMerge(SObject mergeToRecord, Iterable duplicatedRecordIds) { - Record dmlMergeToRecord = Record(mergeToRecord); - return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Records(duplicatedRecordIds)); - } - - private Commitable toMerge(MergeStrategy mergeStrategy, Records duplicateRecords) { - return this.registerInLinearOrchestrator(mergeStrategy, duplicateRecords); - } - - private MergeStrategy getMergeStrategy(SObjectType objectType, Record mergeToRecord) { - return new MergeStrategy(objectType, this.configuration, mergeToRecord); - } - - // Platform Event - - global Commitable toPublish(SObject record) { - Record dmlRecord = Record(record); - return this.registerInLinearOrchestrator(this.getPlatformEventStrategy(dmlRecord.getSObjectType()), dmlRecord); - } - - global Commitable toPublish(List records) { - Records dmlRecords = Records(records); - return this.registerInLinearOrchestrator(this.getPlatformEventStrategy(dmlRecords.getSObjectType()), dmlRecords); - } - - global OperationResult publishImmediately(SObject record) { - Record dmlRecord = Record(record); - return this.executeImmediately(this.getPlatformEventStrategy(dmlRecord.getSObjectType()), dmlRecord); - } - - global OperationResult publishImmediately(List records) { - Records dmlRecords = Records(records); - return this.executeImmediately(this.getPlatformEventStrategy(dmlRecords.getSObjectType()), dmlRecords); - } - - private PlatformEventStrategy getPlatformEventStrategy(SObjectType objectType) { - return new PlatformEventStrategy(objectType, this.configuration); - } - - // Helpers - - private OperationResult executeImmediately(DmlStrategy strategy, Record record) { - DmlStrategy existing = this.strategiesStorage.intern(strategy); - OperationResult result = existing.execute(new List{ record.get() }); - this.storeResult(result); - return result; - } - - private OperationResult executeImmediately(DmlStrategy strategy, Records records) { - DmlStrategy existing = this.strategiesStorage.intern(strategy); - OperationResult result = existing.execute(records); - this.storeResult(result); - return result; - } - - private Commitable registerInDependencyOrchestrator(DmlStrategy strategy, Record record) { - DmlStrategy existing = this.strategiesStorage.intern(strategy); - this.dependencyOrchestrator.register(existing, record); - return this; - } - - private Commitable registerInDependencyOrchestrator(DmlStrategy strategy, Records records) { - DmlStrategy existing = this.strategiesStorage.intern(strategy); - this.dependencyOrchestrator.register(existing, records); - return this; - } - - private Commitable registerInLinearOrchestrator(DmlStrategy strategy, Record record) { - DmlStrategy existing = this.strategiesStorage.intern(strategy); - this.linearOrchestrator.register(existing, record); - return this; - } - - private Commitable registerInLinearOrchestrator(DmlStrategy strategy, Records records) { - DmlStrategy existing = this.strategiesStorage.intern(strategy); - this.linearOrchestrator.register(existing, records); - return this; - } - - // Identifier - - global Commitable identifier(String dmlIdentifier) { - this.configuration.identifier(dmlIdentifier); - return this; - } - - // Debug - - global void preview() { - this.configuration.preview(); - } - - // Field Level Security - - global Commitable userMode() { - return this.setAccessMode(System.AccessLevel.USER_MODE); - } - - global Commitable systemMode() { - return this.setAccessMode(System.AccessLevel.SYSTEM_MODE); - } - - private Commitable setAccessMode(System.AccessLevel accessMode) { - this.configuration.accessMode(accessMode); - return this; - } + public Commitable toUndelete(List records) { + return this.toUndelete(Records(records)); + } + + private Commitable toUndelete(Records records) { + return this.registerInLinearOrchestrator(this.getUndeleteStrategy(records.getSObjectType()), records); + } + + public OperationResult undeleteImmediately(Id recordId) { + return this.undeleteImmediately(Record(recordId)); + } + + public OperationResult undeleteImmediately(SObject record) { + return this.undeleteImmediately(Record(record)); + } + + private OperationResult undeleteImmediately(Record record) { + return this.executeImmediately(this.getUndeleteStrategy(record.getSObjectType()), record); + } + + public OperationResult undeleteImmediately(Iterable recordIds) { + return this.undeleteImmediately(Records(recordIds)); + } + + public OperationResult undeleteImmediately(List records) { + return this.undeleteImmediately(Records(records)); + } + + private OperationResult undeleteImmediately(Records records) { + return this.executeImmediately(this.getUndeleteStrategy(records.getSObjectType()), records); + } + + private UndeleteStrategy getUndeleteStrategy(SObjectType objectType) { + return new UndeleteStrategy(objectType, this.configuration); + } + + // Merge + + public Commitable toMerge(SObject mergeToRecord, SObject duplicatedRecord) { + Record dmlMergeToRecord = Record(mergeToRecord); + return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Record(duplicatedRecord)); + } + + public Commitable toMerge(SObject mergeToRecord, Id duplicatedRecordId) { + Record dmlMergeToRecord = Record(mergeToRecord); + return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Record(duplicatedRecordId)); + } + + private Commitable toMerge(MergeStrategy mergeStrategy, Record duplicatedRecord) { + return this.registerInLinearOrchestrator(mergeStrategy, duplicatedRecord); + } + + public Commitable toMerge(SObject mergeToRecord, List duplicateRecords) { + Record dmlMergeToRecord = Record(mergeToRecord); + return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Records(duplicateRecords)); + } + + public Commitable toMerge(SObject mergeToRecord, Iterable duplicatedRecordIds) { + Record dmlMergeToRecord = Record(mergeToRecord); + return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Records(duplicatedRecordIds)); + } + + private Commitable toMerge(MergeStrategy mergeStrategy, Records duplicateRecords) { + return this.registerInLinearOrchestrator(mergeStrategy, duplicateRecords); + } + + private MergeStrategy getMergeStrategy(SObjectType objectType, Record mergeToRecord) { + return new MergeStrategy(objectType, this.configuration, mergeToRecord); + } + + // Platform Event + + public Commitable toPublish(SObject record) { + Record dmlRecord = Record(record); + return this.registerInLinearOrchestrator(this.getPlatformEventStrategy(dmlRecord.getSObjectType()), dmlRecord); + } + + public Commitable toPublish(List records) { + Records dmlRecords = Records(records); + return this.registerInLinearOrchestrator(this.getPlatformEventStrategy(dmlRecords.getSObjectType()), dmlRecords); + } + + public OperationResult publishImmediately(SObject record) { + Record dmlRecord = Record(record); + return this.executeImmediately(this.getPlatformEventStrategy(dmlRecord.getSObjectType()), dmlRecord); + } + + public OperationResult publishImmediately(List records) { + Records dmlRecords = Records(records); + return this.executeImmediately(this.getPlatformEventStrategy(dmlRecords.getSObjectType()), dmlRecords); + } + + private PlatformEventStrategy getPlatformEventStrategy(SObjectType objectType) { + return new PlatformEventStrategy(objectType, this.configuration); + } + + // Helpers + + private OperationResult executeImmediately(DmlStrategy strategy, Record record) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + OperationResult result = existing.execute(new List{ record.get() }); + this.storeResult(result); + return result; + } + + private OperationResult executeImmediately(DmlStrategy strategy, Records records) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + OperationResult result = existing.execute(records); + this.storeResult(result); + return result; + } + + private Commitable registerInDependencyOrchestrator(DmlStrategy strategy, Record record) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + this.dependencyOrchestrator.register(existing, record); + return this; + } + + private Commitable registerInDependencyOrchestrator(DmlStrategy strategy, Records records) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + this.dependencyOrchestrator.register(existing, records); + return this; + } + + private Commitable registerInLinearOrchestrator(DmlStrategy strategy, Record record) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + this.linearOrchestrator.register(existing, record); + return this; + } + + private Commitable registerInLinearOrchestrator(DmlStrategy strategy, Records records) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + this.linearOrchestrator.register(existing, records); + return this; + } + + // Identifier + + public Commitable identifier(String dmlIdentifier) { + this.configuration.identifier(dmlIdentifier); + return this; + } + + // Debug + + public void preview() { + this.configuration.preview(); + } + + // Field Level Security + + public Commitable userMode() { + return this.setAccessMode(System.AccessLevel.USER_MODE); + } + + public Commitable systemMode() { + return this.setAccessMode(System.AccessLevel.SYSTEM_MODE); + } + + private Commitable setAccessMode(System.AccessLevel accessMode) { + this.configuration.accessMode(accessMode); + return this; + } - // Sharing Mode + // Sharing Mode - global Commitable withSharing() { - this.configuration.withSharing(); - return this; - } + public Commitable withSharing() { + this.configuration.withSharing(); + return this; + } - global Commitable withoutSharing() { - this.configuration.withoutSharing(); - return this; - } + public Commitable withoutSharing() { + this.configuration.withoutSharing(); + return this; + } - // Other configs + // Other configs - global Commitable allowPartialSuccess() { - this.configuration.allowPartialSuccess(); - return this; - } + public Commitable allowPartialSuccess() { + this.configuration.allowPartialSuccess(); + return this; + } - global Commitable skipDuplicateRules() { - this.configuration.skipDuplicateRules(); - return this; - } - - global Commitable combineOnDuplicate() { - this.configuration.combineOnDuplicate(); - return this; - } - - global Commitable options(Database.DmlOptions options) { - this.configuration.options(options); - return this; - } + public Commitable skipDuplicateRules() { + this.configuration.skipDuplicateRules(); + return this; + } + + public Commitable combineOnDuplicate() { + this.configuration.combineOnDuplicate(); + return this; + } + + public Commitable options(Database.DmlOptions options) { + this.configuration.options(options); + return this; + } - global Commitable discardWork() { - this.reset(); - return this; - } + public Commitable discardWork() { + this.reset(); + return this; + } - // Hooks + // Hooks - global Commitable commitHook(Hook callback) { - this.hook = callback; - return this; - } + public Commitable commitHook(Hook callback) { + this.hook = callback; + return this; + } - global Result dryRun() { - Savepoint savePoint = Database.setSavepoint(); + public Result dryRun() { + Savepoint savePoint = Database.setSavepoint(); - try { - DmlResult result = new DmlResult(); + try { + DmlResult result = new DmlResult(); - this.hook?.before(); + this.hook?.before(); - this.dependencyOrchestrator.execute(result); - this.linearOrchestrator.execute(result); + this.dependencyOrchestrator.execute(result); + this.linearOrchestrator.execute(result); - this.hook?.after(result); + this.hook?.after(result); - return result; - } finally { - Database.rollback(savePoint); - Database.releaseSavepoint(savePoint); - } - } + return result; + } finally { + Database.rollback(savePoint); + Database.releaseSavepoint(savePoint); + } + } - global Result commitWork() { - DmlResult result = new DmlResult(); + public Result commitWork() { + DmlResult result = new DmlResult(); - try { - this.hook?.before(); + try { + this.hook?.before(); - this.dependencyOrchestrator.execute(result); - this.linearOrchestrator.execute(result); + this.dependencyOrchestrator.execute(result); + this.linearOrchestrator.execute(result); - this.storeResult(result); + this.storeResult(result); - this.hook?.after(result); - } finally { - this.reset(); - } + this.hook?.after(result); + } finally { + this.reset(); + } - return result; - } + return result; + } - private void storeResult(DmlResult result) { - if (String.isBlank(this.configuration.dmlIdentifier)) { - return; - } + private void storeResult(DmlResult result) { + if (String.isBlank(this.configuration.dmlIdentifier)) { + return; + } - dmlIdentifierToResult.put(this.configuration.dmlIdentifier, result); - } + dmlIdentifierToResult.put(this.configuration.dmlIdentifier, result); + } - private void storeResult(OperationResult operationResult) { - if (String.isBlank(this.configuration.dmlIdentifier)) { - return; - } + private void storeResult(OperationResult operationResult) { + if (String.isBlank(this.configuration.dmlIdentifier)) { + return; + } - DmlResult result = (DmlResult) dmlIdentifierToResult.get(this.configuration.dmlIdentifier); + DmlResult result = (DmlResult) dmlIdentifierToResult.get(this.configuration.dmlIdentifier); - if (result == null) { - result = new DmlResult(); - } + if (result == null) { + result = new DmlResult(); + } - result.add(operationResult); + result.add(operationResult); - dmlIdentifierToResult.put(this.configuration.dmlIdentifier, result); - } + dmlIdentifierToResult.put(this.configuration.dmlIdentifier, result); + } - global Result commitTransaction() { - if (!this.configuration.allOrNone) { - throw new DmlException('commitTransaction() is not supported when allOrNone=false'); - } + public Result commitTransaction() { + if (!this.configuration.allOrNone) { + throw new DmlException('commitTransaction() is not supported when allOrNone=false'); + } - Savepoint savePoint = Database.setSavepoint(); + Savepoint savePoint = Database.setSavepoint(); - try { - return this.commitWork(); - } catch (Exception e) { - Database.rollback(savePoint); - throw e; - } finally { - Database.releaseSavepoint(savePoint); - } - } + try { + return this.commitWork(); + } catch (Exception e) { + Database.rollback(savePoint); + throw e; + } finally { + Database.releaseSavepoint(savePoint); + } + } - private void reset() { - this.dependencyOrchestrator = new DependencyOrchestrator(); - this.linearOrchestrator = new LinearOrchestrator(this.configuration); - } + private void reset() { + this.dependencyOrchestrator = new DependencyOrchestrator(); + this.linearOrchestrator = new LinearOrchestrator(this.configuration); + } - private class StrategiesStorage { - private Map strategyByOperationId = new Map(); + private class StrategiesStorage { + private Map strategyByOperationId = new Map(); - private DmlStrategy intern(DmlStrategy candidate) { - String operationId = candidate.operationId(); + private DmlStrategy intern(DmlStrategy candidate) { + String operationId = candidate.operationId(); - DmlStrategy existing = this.strategyByOperationId.get(operationId); + DmlStrategy existing = this.strategyByOperationId.get(operationId); - if (existing == null) { - this.strategyByOperationId.put(operationId, candidate); - return candidate; - } + if (existing == null) { + this.strategyByOperationId.put(operationId, candidate); + return candidate; + } - return existing; - } - } + return existing; + } + } - private class DependencyOrchestrator { - private Map enhancedRecordsById = new Map(); - private List enhancedRecords = new List(); + private class DependencyOrchestrator { + private Map enhancedRecordsById = new Map(); + private List enhancedRecords = new List(); - private OrderDependencyGraph orderDependencyGraph = new OrderDependencyGraph(); + private OrderDependencyGraph orderDependencyGraph = new OrderDependencyGraph(); - private void register(DmlStrategy strategy, Records records) { - for (Record record : records.get()) { - this.register(strategy, record); - } - } + private void register(DmlStrategy strategy, Records records) { + for (Record record : records.get()) { + this.register(strategy, record); + } + } - private void register(DmlStrategy strategy, Record record) { - EnhancedRecord enhancedRecord = record.get(); + private void register(DmlStrategy strategy, Record record) { + EnhancedRecord enhancedRecord = record.get(); - strategy.validate(enhancedRecord); - enhancedRecord.setDatabaseStrategy(strategy); + strategy.validate(enhancedRecord); + enhancedRecord.setDatabaseStrategy(strategy); - this.enhancedRecords.add(enhancedRecord); - } + this.enhancedRecords.add(enhancedRecord); + } - private void execute(DmlResult result) { - this.addTemporaryIdsToBuildDependencyGraph(); + private void execute(DmlResult result) { + this.addTemporaryIdsToBuildDependencyGraph(); - List> executionWaves = this.orderDependencyGraph.computeExecutionWaves(); + List> executionWaves = this.orderDependencyGraph.computeExecutionWaves(); - for (List waveRecordIds : executionWaves) { - for (ProcessingGroup processingGroup : this.groupRecordsByStrategy(waveRecordIds)) { - result.add(processingGroup.strategy.commitWork(processingGroup.records)); - } - } - } + for (List waveRecordIds : executionWaves) { + for (ProcessingGroup processingGroup : this.groupRecordsByStrategy(waveRecordIds)) { + result.add(processingGroup.strategy.commitWork(processingGroup.records)); + } + } + } - private void addTemporaryIdsToBuildDependencyGraph() { - for (EnhancedRecord enhancedRecord : this.enhancedRecords) { - enhancedRecord.setTemporaryId(); - enhancedRecord.addToDependencyGraph(this.orderDependencyGraph); + private void addTemporaryIdsToBuildDependencyGraph() { + for (EnhancedRecord enhancedRecord : this.enhancedRecords) { + enhancedRecord.setTemporaryId(); + enhancedRecord.addToDependencyGraph(this.orderDependencyGraph); - this.enhancedRecordsById.put(enhancedRecord.getRecordId(), enhancedRecord); - } - } + this.enhancedRecordsById.put(enhancedRecord.getRecordId(), enhancedRecord); + } + } - private List groupRecordsByStrategy(List enhancedRecordIds) { - Map processingGroupsByOperationId = new Map(); + private List groupRecordsByStrategy(List enhancedRecordIds) { + Map processingGroupsByOperationId = new Map(); - for (Id enhancedRecordId : enhancedRecordIds) { - EnhancedRecord enhancedRecord = this.enhancedRecordsById.get(enhancedRecordId); + for (Id enhancedRecordId : enhancedRecordIds) { + EnhancedRecord enhancedRecord = this.enhancedRecordsById.get(enhancedRecordId); - if (enhancedRecord == null) { - // record was commited without unit of work, or executeImmediately was used - // so it was not registered in the dependency graph - continue; - } + if (enhancedRecord == null) { + // record was commited without unit of work, or executeImmediately was used + // so it was not registered in the dependency graph + continue; + } - enhancedRecord.clearTemporaryId(); + enhancedRecord.clearTemporaryId(); - String operationId = enhancedRecord.strategy.operationId(); + String operationId = enhancedRecord.strategy.operationId(); - if (!processingGroupsByOperationId.containsKey(operationId)) { - processingGroupsByOperationId.put(operationId, new ProcessingGroup(enhancedRecord.strategy)); - } + if (!processingGroupsByOperationId.containsKey(operationId)) { + processingGroupsByOperationId.put(operationId, new ProcessingGroup(enhancedRecord.strategy)); + } - processingGroupsByOperationId.get(operationId).add(enhancedRecord); - } + processingGroupsByOperationId.get(operationId).add(enhancedRecord); + } - return processingGroupsByOperationId.values(); - } - } + return processingGroupsByOperationId.values(); + } + } + + private class LinearOrchestrator { + private Map processingGroupByOperationId = new Map(); + private Map> processingGroupsByOperationType = new Map>{ + OperationType.UPDATE_DML => new List(), + OperationType.MERGE_DML => new List(), + OperationType.DELETE_DML => new List(), + OperationType.UNDELETE_DML => new List(), + OperationType.PUBLISH_DML => new List() + }; - private class LinearOrchestrator { - private Map processingGroupByOperationId = new Map(); - private Map> processingGroupsByOperationType = new Map>{ - OperationType.UPDATE_DML => new List(), - OperationType.MERGE_DML => new List(), - OperationType.DELETE_DML => new List(), - OperationType.UNDELETE_DML => new List(), - OperationType.PUBLISH_DML => new List() - }; + private Configuration configuration; - private Configuration configuration; + private LinearOrchestrator(Configuration configuration) { + this.configuration = configuration; + } - private LinearOrchestrator(Configuration configuration) { - this.configuration = configuration; - } + private void register(DmlStrategy strategy, Records records) { + for (Record record : records.get()) { + this.register(strategy, record); + } + } - private void register(DmlStrategy strategy, Records records) { - for (Record record : records.get()) { - this.register(strategy, record); - } - } + private void register(DmlStrategy strategy, Record record) { + EnhancedRecord enhancedRecord = record.get(); - private void register(DmlStrategy strategy, Record record) { - EnhancedRecord enhancedRecord = record.get(); + strategy.validate(enhancedRecord); - strategy.validate(enhancedRecord); + this.assignRecordToProcessingGroup(strategy, enhancedRecord); + } + private void assignRecordToProcessingGroup(DmlStrategy strategy, EnhancedRecord enhancedRecord) { + ProcessingGroup processingGroup = this.processingGroupByOperationId.get(strategy.operationId()); - this.assignRecordToProcessingGroup(strategy, enhancedRecord); - } + if (processingGroup == null) { + processingGroup = new ProcessingGroup(strategy, this.configuration.duplicateCombineStrategy); - private void assignRecordToProcessingGroup(DmlStrategy strategy, EnhancedRecord enhancedRecord) { - ProcessingGroup processingGroup = this.processingGroupByOperationId.get(strategy.operationId()); + this.processingGroupByOperationId.put(strategy.operationId(), processingGroup); + this.processingGroupsByOperationType.get(strategy.getOperationType()).add(processingGroup); + } - if (processingGroup == null) { - processingGroup = new ProcessingGroup(strategy, this.configuration.duplicateCombineStrategy); + if (processingGroup.hasDuplicate(enhancedRecord)) { + processingGroup.executeDuplicateStrategy(enhancedRecord); + return; + } - this.processingGroupByOperationId.put(strategy.operationId(), processingGroup); - this.processingGroupsByOperationType.get(strategy.getOperationType()).add(processingGroup); - } + processingGroup.add(enhancedRecord); + } - if (processingGroup.hasDuplicate(enhancedRecord)) { - processingGroup.executeDuplicateStrategy(enhancedRecord); - return; - } + private void execute(DmlResult result) { + for (OperationType operationType : this.processingGroupsByOperationType.keySet()) { + for (ProcessingGroup processingGroup : this.processingGroupsByOperationType.get(operationType)) { + result.add(processingGroup.strategy.commitWork(processingGroup.records)); + } + } + } + } - processingGroup.add(enhancedRecord); - } + private class ProcessingGroup { + private DmlStrategy strategy; + private DuplicateCombineStrategy duplicateCombineStrategy; - private void execute(DmlResult result) { - for (OperationType operationType : this.processingGroupsByOperationType.keySet()) { - for (ProcessingGroup processingGroup : this.processingGroupsByOperationType.get(operationType)) { - result.add(processingGroup.strategy.commitWork(processingGroup.records)); - } - } - } - } + private Map recordsById = new Map(); + private List records = new List(); - private class ProcessingGroup { - private DmlStrategy strategy; - private DuplicateCombineStrategy duplicateCombineStrategy; + private ProcessingGroup(DmlStrategy strategy) { + this.strategy = strategy; + } - private Map recordsById = new Map(); - private List records = new List(); + private ProcessingGroup(DmlStrategy strategy, DuplicateCombineStrategy duplicateCombineStrategy) { + this(strategy); + this.duplicateCombineStrategy = duplicateCombineStrategy; + } - private ProcessingGroup(DmlStrategy strategy) { - this.strategy = strategy; - } + private Boolean hasDuplicate(EnhancedRecord newRecord) { + return newRecord.hasId() && this.recordsById.containsKey(newRecord.getRecordId()); + } - private ProcessingGroup(DmlStrategy strategy, DuplicateCombineStrategy duplicateCombineStrategy) { - this(strategy); - this.duplicateCombineStrategy = duplicateCombineStrategy; - } + private void executeDuplicateStrategy(EnhancedRecord newRecord) { + EnhancedRecord existingRecord = this.recordsById.get(newRecord.getRecordId()); - private Boolean hasDuplicate(EnhancedRecord newRecord) { - return newRecord.hasId() && this.recordsById.containsKey(newRecord.getRecordId()); - } + this.duplicateCombineStrategy.combine(existingRecord, newRecord); + } - private void executeDuplicateStrategy(EnhancedRecord newRecord) { - EnhancedRecord existingRecord = this.recordsById.get(newRecord.getRecordId()); + private void add(EnhancedRecord newRecord) { + this.records.add(newRecord); - this.duplicateCombineStrategy.combine(existingRecord, newRecord); - } + if (newRecord.hasId()) { + this.recordsById.put(newRecord.getRecordId(), newRecord); + } + } + } - private void add(EnhancedRecord newRecord) { - this.records.add(newRecord); + private class DmlResult implements Result { + private Map> operationResultsByObjectType = new Map>(); - if (newRecord.hasId()) { - this.recordsById.put(newRecord.getRecordId(), newRecord); - } - } - } + private DmlResult() { + for (OperationType operationType : OperationType.values()) { + this.operationResultsByObjectType.put(operationType, new Map()); + } + } + + private DmlResult add(OperationResult result) { + OperationType operationType = result.operationType(); + SObjectType objectType = result.objectType(); - private class DmlResult implements Result { - private Map> operationResultsByObjectType = new Map>(); + SObjectAggregatedResult aggregatedResult = this.operationResultsByObjectType.get(operationType).get(objectType); - private DmlResult() { - for (OperationType operationType : OperationType.values()) { - this.operationResultsByObjectType.put(operationType, new Map()); - } - } + if (aggregatedResult == null) { + aggregatedResult = new SObjectAggregatedResult(operationType, objectType); + this.operationResultsByObjectType.get(operationType).put(objectType, aggregatedResult); + } - private DmlResult add(OperationResult result) { - OperationType operationType = result.operationType(); - SObjectType objectType = result.objectType(); + aggregatedResult.add(result); - SObjectAggregatedResult aggregatedResult = this.operationResultsByObjectType.get(operationType).get(objectType); + return this; + } - if (aggregatedResult == null) { - aggregatedResult = new SObjectAggregatedResult(operationType, objectType); - this.operationResultsByObjectType.get(operationType).put(objectType, aggregatedResult); - } + public List inserts() { + return this.getOperationResults(OperationType.INSERT_DML); + } - aggregatedResult.add(result); + public List updates() { + return this.getOperationResults(OperationType.UPDATE_DML); + } - return this; - } + public List upserts() { + return this.getOperationResults(OperationType.UPSERT_DML); + } - public List inserts() { - return this.getOperationResults(OperationType.INSERT_DML); - } + public List deletes() { + return this.getOperationResults(OperationType.DELETE_DML); + } + + public List undeletes() { + return this.getOperationResults(OperationType.UNDELETE_DML); + } + + public List merges() { + return this.getOperationResults(OperationType.MERGE_DML); + } + + public List events() { + return this.getOperationResults(OperationType.PUBLISH_DML); + } + + public OperationResult insertsOf(SObjectType objectType) { + return this.getOperationResult(OperationType.INSERT_DML, objectType); + } + + public OperationResult updatesOf(SObjectType objectType) { + return this.getOperationResult(OperationType.UPDATE_DML, objectType); + } + + public OperationResult upsertsOf(SObjectType objectType) { + return this.getOperationResult(OperationType.UPSERT_DML, objectType); + } + + public OperationResult deletesOf(SObjectType objectType) { + return this.getOperationResult(OperationType.DELETE_DML, objectType); + } + + public OperationResult undeletesOf(SObjectType objectType) { + return this.getOperationResult(OperationType.UNDELETE_DML, objectType); + } + + public OperationResult mergesOf(SObjectType objectType) { + return this.getOperationResult(OperationType.MERGE_DML, objectType); + } + + public OperationResult eventsOf(SObjectType objectType) { + return this.getOperationResult(OperationType.PUBLISH_DML, objectType); + } + + private List getOperationResults(OperationType operationType) { + return this.operationResultsByObjectType.get(operationType).values(); + } + + private OperationResult getOperationResult(OperationType operationType, SObjectType objectType) { + return this.operationResultsByObjectType.get(operationType).get(objectType) ?? new SObjectAggregatedResult(operationType, objectType); + } + + public List all() { + List allResults = new List(); + + for (OperationType operationType : this.operationResultsByObjectType.keySet()) { + allResults.addAll(this.operationResultsByObjectType.get(operationType).values()); + } + + return allResults; + } + } + + private class Configuration { + private System.AccessLevel accessMode = System.AccessLevel.USER_MODE; + private Database.DmlOptions options = new Database.DmlOptions(); + private DmlSharing sharingExecutor = new InheritedSharing(); + private DuplicateCombineStrategy duplicateCombineStrategy = new ThrownExceptionDuplicateStrategy(); + private Boolean allOrNone = true; + private String dmlIdentifier = null; + + private Configuration() { + this.options.optAllOrNone = true; + } + + private void accessMode(System.AccessLevel accessMode) { + this.accessMode = accessMode; + } + + private void withSharing() { + this.sharingExecutor = new WithSharing(); + } + + private void withoutSharing() { + this.sharingExecutor = new WithoutSharing(); + } + + private void identifier(String dmlIdentifier) { + this.dmlIdentifier = dmlIdentifier; + } + + private void allowPartialSuccess() { + this.allOrNone = false; + this.options.optAllOrNone = false; + } + + private void combineOnDuplicate() { + this.duplicateCombineStrategy = new MergeDuplicateStrategy(); + } + + private void options(Database.DmlOptions options) { + this.options = options; + this.options.optAllOrNone = this.options.optAllOrNone ?? this.allOrNone ?? true; + } + + private void preview() { + System.debug( + LoggingLevel.ERROR, + '\n\n============ DML Configuration ============' + + '\nOptions: ' + + JSON.serializePretty(this.options) + + '\nSharing Executor: ' + + String.valueOf(this.sharingExecutor).split(':')[0] + + '\nAll Or None: ' + + this.allOrNone + + '\nDML Identifier: ' + + this.dmlIdentifier + + '\n=======================================\n' + ); + } + + private void skipDuplicateRules() { + this.options.duplicateRuleHeader.allowSave = true; + } + } + + private abstract class DmlStrategy { + private SObjectType objectType; + private Configuration globalConfiguration; + + protected DmlStrategy(SObjectType objectType, Configuration configuration) { + this.objectType = objectType; + this.globalConfiguration = configuration; + } + + private SObjectType getObjectType() { + return this.objectType; + } + + private OperationResult commitWork(List records) { + return this.globalConfiguration.sharingExecutor.execute(this, records); + } + + private OperationResult execute(Records records) { + List enhancedRecords = new List(); + + for (Record record : records.get()) { + enhancedRecords.add(record.get()); + } + + return this.execute(enhancedRecords); + } + + private OperationResult execute(List records) { + List recordsToProcess = this.getRecordsToProcess(records); + + ExecutionResult result = new ExecutionResult(this.getOperationType(), this.objectType).setRecords(recordsToProcess); + + DmlMock dmlMock = dmlIdentifierToMock.get(this.globalConfiguration.dmlIdentifier); + + if (dmlMock != null && dmlMock.shouldBeMocked(this.getOperationType(), this.objectType)) { + return result.setRecordResults(dmlMock.getMockedRecordResults(this, recordsToProcess)); + } + + return result.setRecordResults(this.getResultAdapter().get(this.executeDml(recordsToProcess), recordsToProcess)); + } + + private List getRecordsToProcess(List records) { + List recordsToProcess = new List(); + + for (EnhancedRecord enhancedRecord : records) { + enhancedRecord.resolveRecordRelationships(); + recordsToProcess.add(enhancedRecord.getRecord()); + } + + return recordsToProcess; + } + + protected abstract OperationType getOperationType(); + protected abstract DmlResultAdapter getResultAdapter(); + protected abstract List executeDml(List recordsToProcess); + protected abstract RecordSummary executeMockedDml(SObject record); + + protected virtual void validate(EnhancedRecord enhancedRecord) { + return; + } + + protected virtual String operationId() { + return this.getOperationType().name() + '_' + (this.getObjectType()?.toString() ?? 'EMPTY'); + } + } + + // Strategies + + private inherited sharing class InsertStrategy extends DmlStrategy { + private InsertStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + protected override OperationType getOperationType() { + return OperationType.INSERT_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new SaveResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (enhancedRecord.hasId()) { + throw new DmlException('Only new records can be registered as new.'); + } + } + + protected override List executeDml(List recordsToProcess) { + return Database.insert(recordsToProcess, this.globalConfiguration.options, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + record.put('Id', randomIdGenerator.get(record.getSObjectType())); + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class UpsertStrategy extends DmlStrategy { + private SObjectField externalIdField; + + private UpsertStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + private UpsertStrategy withExternalIdField(SObjectField externalIdField) { + this.externalIdField = externalIdField; + return this; + } + + protected override String operationId() { + String externalId = this.externalIdField == null ? 'Id' : this.externalIdField.toString(); + return super.operationId() + '_' + externalId; + } + + protected override OperationType getOperationType() { + return OperationType.UPSERT_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new UpsertResultAdapter(); + } + + protected override List executeDml(List recordsToProcess) { + if (this.externalIdField == null) { + return Database.upsert(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + } + + return Database.upsert(recordsToProcess, this.externalIdField, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + if (record.Id == null) { + record.put('Id', randomIdGenerator.get(record.getSObjectType())); + } + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class UpdateStrategy extends DmlStrategy { + private UpdateStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + protected override OperationType getOperationType() { + return OperationType.UPDATE_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new SaveResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (!enhancedRecord.hasId()) { + throw new DmlException('Only existing records can be updated.'); + } + } + + protected override List executeDml(List recordsToProcess) { + return Database.update(recordsToProcess, this.globalConfiguration.options, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class MergeStrategy extends DmlStrategy { + private EnhancedRecord mergeToRecord; + + private MergeStrategy(SObjectType objectType, Configuration configuration, Record mergeToRecord) { + super(objectType, configuration); + this.validate(mergeToRecord.get()); + this.mergeToRecord = mergeToRecord.get(); + } + + protected override String operationId() { + return super.operationId() + '_' + this.mergeToRecord.getRecordId(); + } + + protected override OperationType getOperationType() { + return OperationType.MERGE_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new MergeResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (!enhancedRecord.hasId()) { + throw new DmlException('Only existing records can be merged.'); + } + } + + protected override List executeDml(List recordsToProcess) { + return Database.merge(this.mergeToRecord.getRecord(), recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class DeleteStrategy extends DmlStrategy { + private Boolean makeHardDelete = false; + + private DeleteStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + private DeleteStrategy withHardDelete() { + this.makeHardDelete = true; + return this; + } + + protected override String operationId() { + return super.operationId() + '_' + this.makeHardDelete; + } + + protected override OperationType getOperationType() { + return OperationType.DELETE_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new DeleteResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (!enhancedRecord.hasId()) { + throw new DmlException('Only existing records can be registered as deleted.'); + } + } + + protected override List executeDml(List recordsToProcess) { + List dmlResults = Database.delete(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + + if (this.makeHardDelete) { + Database.emptyRecycleBin(recordsToProcess); + } + + return dmlResults; + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class UndeleteStrategy extends DmlStrategy { + private UndeleteStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + protected override OperationType getOperationType() { + return OperationType.UNDELETE_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new UndeleteResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (!enhancedRecord.hasId()) { + throw new DmlException('Only deleted records can be undeleted.'); + } + } + + protected override List executeDml(List recordsToProcess) { + return Database.undelete(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class PlatformEventStrategy extends DmlStrategy { + private PlatformEventStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + protected override OperationType getOperationType() { + return OperationType.PUBLISH_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new SaveResultAdapter(); + } + + protected override List executeDml(List recordsToProcess) { + return EventBus.publish(recordsToProcess); + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(randomIdGenerator.get(record.getSObjectType())).record(record); + } + } + + // Sharing Executors + + private interface DmlSharing { + OperationResult execute(DmlStrategy strategy, List records); + } + + private inherited sharing class InheritedSharing implements DmlSharing { + public OperationResult execute(DmlStrategy strategy, List records) { + return strategy.execute(records); + } + } + + private without sharing class WithoutSharing implements DmlSharing { + public OperationResult execute(DmlStrategy strategy, List records) { + return strategy.execute(records); + } + } + + private with sharing class WithSharing implements DmlSharing { + public OperationResult execute(DmlStrategy strategy, List records) { + return strategy.execute(records); + } + } + + // Result Adapters + + private abstract class DmlResultAdapter { + public List get(List dmlResults, List processedRecords) { + List recordResults = new List(); + + for (Integer i = 0; i < dmlResults.size(); i++) { + recordResults.add(this.transform(dmlResults[i]).record(processedRecords[i])); + } + + return recordResults; + } + + protected abstract RecordSummary transform(Object result); + } + + private class SaveResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.SaveResult saveResult = (Database.SaveResult) result; + + return new RecordSummary().isSuccess(saveResult.isSuccess()).recordId(saveResult.getId()).errors(saveResult.getErrors()); + } + } + + private class UpsertResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.UpsertResult upsertResult = (Database.UpsertResult) result; + + return new RecordSummary().isSuccess(upsertResult.isSuccess()).recordId(upsertResult.getId()).errors(upsertResult.getErrors()); + } + } + + private class MergeResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.MergeResult mergeResult = (Database.MergeResult) result; + + return new RecordSummary().isSuccess(mergeResult.isSuccess()).recordId(mergeResult.getId()).errors(mergeResult.getErrors()); + } + } + + private class DeleteResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.DeleteResult deleteResult = (Database.DeleteResult) result; + + return new RecordSummary().isSuccess(deleteResult.isSuccess()).recordId(deleteResult.getId()).errors(deleteResult.getErrors()); + } + } + + private class UndeleteResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.UndeleteResult undeleteResult = (Database.UndeleteResult) result; + + return new RecordSummary().isSuccess(undeleteResult.isSuccess()).recordId(undeleteResult.getId()).errors(undeleteResult.getErrors()); + } + } + + private interface DuplicateCombineStrategy { + void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord); + } + + private class ThrownExceptionDuplicateStrategy implements DuplicateCombineStrategy { + public void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord) { + throw new DmlException('Duplicate records found during registration. Fix the code or use the combineOnDuplicate() method.'); + } + } + + private class MergeDuplicateStrategy implements DuplicateCombineStrategy { + public void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord) { + SObject existingRecord = mergeToRecord.getRecord(); + SObject newRecord = duplicateRecord.getRecord(); + + for (String field : newRecord.getPopulatedFieldsAsMap().keySet()) { + existingRecord.put(field, newRecord.get(field)); + } + } + } + + private abstract class DmlOperationResult implements OperationResult { + private OperationType type; + private SObjectType objectType; + private List recordResults = new List(); + private List records = new List(); + + private Boolean isResultsCached = false; + private List cachedSuccesses = new List(); + private List cachedFailures = new List(); + private List cachedErrors = new List(); + + private DmlOperationResult(OperationType type, SObjectType objectType) { + this.type = type; + this.objectType = objectType; + } + + public SObjectType objectType() { + return this.objectType; + } + + public OperationType operationType() { + return this.type; + } + + public Boolean hasFailures() { + return !this.failures().isEmpty(); + } + + public List errors() { + this.cacheResults(); + return this.cachedErrors; + } + + public List records() { + return this.records; + } + + public List successes() { + this.cacheResults(); + return this.cachedSuccesses; + } + + public List failures() { + this.cacheResults(); + return this.cachedFailures; + } + + public List recordResults() { + return this.recordResults; + } + + private void cacheResults() { + if (this.isResultsCached) { + return; + } + + for (RecordResult recordResult : this.recordResults) { + if (recordResult.isSuccess()) { + this.cachedSuccesses.add(recordResult.record()); + continue; + } + + this.cachedErrors.addAll(recordResult.errors()); + this.cachedFailures.add(recordResult.record()); + } + + this.isResultsCached = true; + } + } + + private class ExecutionResult extends DmlOperationResult { + private ExecutionResult(OperationType type, SObjectType objectType) { + super(type, objectType); + } + + private ExecutionResult setRecords(List records) { + this.records = records; + return this; + } + + private ExecutionResult setRecordResults(List recordResults) { + this.recordResults = recordResults; + return this; + } + } + + private class SObjectAggregatedResult extends DmlOperationResult { + private SObjectAggregatedResult(OperationType type, SObjectType objectType) { + super(type, objectType); + } + + private SObjectAggregatedResult add(OperationResult recordResult) { + this.recordResults.addAll(recordResult.recordResults()); + this.records.addAll(recordResult.records()); + return this; + } + } + + private class RecordSummary implements RecordResult { + private Id recordId; + private SObject record; + private Boolean isSuccess = false; + private List errors = new List(); + + public Id id() { + return this.recordId; + } + + public SObject record() { + return this.record; + } + + public Boolean isSuccess() { + return this.isSuccess; + } + + public List errors() { + return this.errors; + } + + private RecordSummary recordId(Id recordId) { + this.recordId = recordId; + return this; + } + + private RecordSummary record(SObject record) { + this.record = record; + return this; + } + + @SuppressWarnings('PMD.AvoidBooleanMethodParameters') + private RecordSummary isSuccess(Boolean isSuccess) { + this.isSuccess = isSuccess; + return this; + } + + private RecordSummary error(Error error) { + this.errors.add(error); + return this; + } + + private RecordSummary errors(List errors) { + for (Database.Error error : errors ?? new List()) { + this.errors.add(new RecordProcessingError(error)); + } + return this; + } + } + + private class RecordProcessingError implements Error { + private String message; + private System.StatusCode statusCode; + private List fields; + + private RecordProcessingError() { + // Default constructor for builder pattern + } + + private RecordProcessingError(Database.Error error) { + this.message = error.getMessage(); + this.statusCode = error.getStatusCode(); + this.fields = error.getFields(); + } + + private RecordProcessingError setMessage(String message) { + this.message = message; + return this; + } + + private RecordProcessingError setStatusCode(System.StatusCode statusCode) { + this.statusCode = statusCode; + return this; + } + + private RecordProcessingError setFields(List fields) { + this.fields = fields; + return this; + } + + public String message() { + return this.message; + } + + public System.StatusCode statusCode() { + return this.statusCode; + } + + public List fields() { + return this.fields; + } + } + + private class DmlMock implements Mockable { + private Set mockedDmlTypes = new Set(); + private Set thrownExceptionDmlTypes = new Set(); + + private Map> mockedObjectTypesByDmlType = new Map>(); + private Map> thrownExceptionDmlTypesByObjectTypes = new Map>(); + + public Mockable allDmls() { + this.mockedDmlTypes.addAll(OperationType.values()); + return this; + } + + public Mockable allInserts() { + return this.thenMockDml(OperationType.INSERT_DML); + } + + public Mockable allUpdates() { + return this.thenMockDml(OperationType.UPDATE_DML); + } + + public Mockable allUpserts() { + return this.thenMockDml(OperationType.UPSERT_DML); + } + + public Mockable allDeletes() { + return this.thenMockDml(OperationType.DELETE_DML); + } + + public Mockable allUndeletes() { + return this.thenMockDml(OperationType.UNDELETE_DML); + } + + public Mockable allMerges() { + return this.thenMockDml(OperationType.MERGE_DML); + } + + public Mockable allPublishes() { + return this.thenMockDml(OperationType.PUBLISH_DML); + } + + private Mockable thenMockDml(OperationType dmlType) { + this.mockedDmlTypes.add(dmlType); + return this; + } + + public Mockable insertsFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.INSERT_DML, objectType); + } + + public Mockable updatesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.UPDATE_DML, objectType); + } + + public Mockable upsertsFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.UPSERT_DML, objectType); + } + + public Mockable deletesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.DELETE_DML, objectType); + } + + public Mockable undeletesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.UNDELETE_DML, objectType); + } + + public Mockable mergesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.MERGE_DML, objectType); + } + + public Mockable publishesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.PUBLISH_DML, objectType); + } + + private Mockable thenMockDmlFor(OperationType dmlType, SObjectType objectType) { + if (!this.mockedObjectTypesByDmlType.containsKey(dmlType)) { + this.mockedObjectTypesByDmlType.put(dmlType, new Set()); + } + this.mockedObjectTypesByDmlType.get(dmlType).add(objectType); + return this; + } + + public Mockable exceptionOnInserts() { + return this.thenExceptionOn(OperationType.INSERT_DML); + } + + public Mockable exceptionOnUpdates() { + return this.thenExceptionOn(OperationType.UPDATE_DML); + } + + public Mockable exceptionOnUpserts() { + return this.thenExceptionOn(OperationType.UPSERT_DML); + } + + public Mockable exceptionOnDeletes() { + return this.thenExceptionOn(OperationType.DELETE_DML); + } + + public Mockable exceptionOnUndeletes() { + return this.thenExceptionOn(OperationType.UNDELETE_DML); + } + + public Mockable exceptionOnMerges() { + return this.thenExceptionOn(OperationType.MERGE_DML); + } + + public Mockable exceptionOnPublishes() { + return this.thenExceptionOn(OperationType.PUBLISH_DML); + } + + private Mockable thenExceptionOn(OperationType dmlType) { + this.thrownExceptionDmlTypes.add(dmlType); + return this; + } + + public Mockable exceptionOnInsertsFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.INSERT_DML, objectType); + } + + public Mockable exceptionOnUpdatesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.UPDATE_DML, objectType); + } + + public Mockable exceptionOnUpsertsFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.UPSERT_DML, objectType); + } + + public Mockable exceptionOnDeletesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.DELETE_DML, objectType); + } + + public Mockable exceptionOnUndeletesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.UNDELETE_DML, objectType); + } + + public Mockable exceptionOnMergesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.MERGE_DML, objectType); + } + + public Mockable exceptionOnPublishesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.PUBLISH_DML, objectType); + } + + private Mockable thenExceptionOnFor(OperationType dmlType, SObjectType objectType) { + if (!this.thrownExceptionDmlTypesByObjectTypes.containsKey(dmlType)) { + this.thrownExceptionDmlTypesByObjectTypes.put(dmlType, new Set()); + } + this.thrownExceptionDmlTypesByObjectTypes.get(dmlType).add(objectType); + return this; + } + + private Boolean shouldBeMocked(OperationType dmlType, SObjectType objectType) { + return this.mockedDmlTypes.contains(dmlType) || + (this.mockedObjectTypesByDmlType.get(dmlType) ?? new Set()).contains(objectType) || + this.shouldThrowException(dmlType, objectType); + } + + private List getMockedRecordResults(DmlStrategy strategy, List recordsToProcess) { + if (this.shouldThrowException(strategy.getOperationType(), strategy.getObjectType())) { + if (strategy.globalConfiguration.allOrNone) { + throw new DmlException('Exception thrown for ' + strategy.getOperationType() + ' operation.'); + } + + // all or none is false, so we need to return a list of record results with errors + return this.getMockedRecordErrors(strategy, recordsToProcess); + } + + return this.getMockedRecordSuccesses(strategy, recordsToProcess); + } + + private List getMockedRecordErrors(DmlStrategy strategy, List recordsToProcess) { + List recordResults = new List(); + String errorMessage = 'Exception thrown for ' + strategy.getOperationType() + ' operation.'; + + for (SObject record : recordsToProcess) { + RecordSummary recordSummary = new RecordSummary() + .isSuccess(false) + .error(new RecordProcessingError().setMessage(errorMessage).setStatusCode(System.StatusCode.ALREADY_IN_PROCESS).setFields(new List{ 'Id' })) + .record(record); + + recordResults.add(recordSummary); + } + + return recordResults; + } + + private List getMockedRecordSuccesses(DmlStrategy strategy, List recordsToProcess) { + List recordResults = new List(); + + for (SObject record : recordsToProcess) { + recordResults.add(strategy.executeMockedDml(record)); + } + + return recordResults; + } + + private Boolean shouldThrowException(OperationType dmlType, SObjectType objectType) { + return this.thrownExceptionDmlTypes.contains(dmlType) || (this.thrownExceptionDmlTypesByObjectTypes.get(dmlType) ?? new Set()).contains(objectType); + } + } + + private class DmlRecords implements Records { + private List records = new List(); + private SObjectType objectType; + + private Map valueByFieldApiName = new Map(); + private List parentRelationships = new List(); + private List externalRelationships = new List(); + + private DmlRecords(List records) { + this.objectType = this.resolveObjectType(records); + + for (SObject record : records) { + this.records.add(new DmlRecord(record)); + } + } + + private SObjectType resolveObjectType(List records) { + if (records == null) { + return null; + } + + SObjectType resolvedType = null; + + for (SObject record : records) { + if (record == null) { + continue; + } + + SObjectType currentType = record.getSObjectType(); + if (resolvedType == null) { + resolvedType = currentType; + continue; + } + + if (resolvedType != currentType) { + throw new DmlException('Mixed SObject types in a single List operation are not supported.'); + } + } + + return resolvedType; + } + + private DmlRecords(Iterable recordIds) { + this.setObjectTypeBasedOnIds(recordIds); + + for (Id recordId : recordIds) { + this.records.add(new DmlRecord(recordId)); + } + } + + private void setObjectTypeBasedOnIds(Iterable recordIds) { + Iterator it = recordIds.iterator(); + if (it.hasNext()) { + this.objectType = it.next().getSObjectType(); + } + } + + public DmlRecords with(SObjectField field, Object value) { + this.valueByFieldApiName.put(field, value); + return this; + } + + public DmlRecords withRelationship(SObjectField relationshipField, SObject relatedToRecord) { + this.parentRelationships.add(new ParentRelationship(relationshipField, new EnhancedRecord(relatedToRecord))); + return this; + } + + public DmlRecords withRelationship(SObjectField relationshipField, SObjectField externalIdField, Object externalId) { + this.externalRelationships.add(new ExternalRelationship(relationshipField, externalIdField, externalId)); + return this; + } + + public List get() { + for (Record record : this.records) { + EnhancedRecord enhancedRecord = record.get(); + + this.populateFieldsValues(enhancedRecord); + this.populateParentRelationships(enhancedRecord); + this.populateExternalRelationships(enhancedRecord); + } + + return this.records; + } + + public SObjectType getSObjectType() { + return this.objectType; + } + + private void populateFieldsValues(EnhancedRecord enhancedRecord) { + for (SObjectField field : this.valueByFieldApiName.keySet()) { + enhancedRecord.with(field, this.valueByFieldApiName.get(field)); + } + } + + private void populateParentRelationships(EnhancedRecord enhancedRecord) { + for (ParentRelationship parentRelationship : this.parentRelationships) { + enhancedRecord.withRelationship(parentRelationship); + } + } + + private void populateExternalRelationships(EnhancedRecord enhancedRecord) { + for (ExternalRelationship externalRelationship : this.externalRelationships) { + enhancedRecord.withRelationship(externalRelationship); + } + } + } + + private class DmlRecord implements Record { + private EnhancedRecord enhancedRecord; + + private DmlRecord(SObject record) { + this.enhancedRecord = new EnhancedRecord(record); + } + + private DmlRecord(Id recordId) { + if (recordId == null) { + throw new DmlException('Invalid argument: recordId. Record ID cannot be null.'); + } + + this.enhancedRecord = new EnhancedRecord(recordId); + } + + public DmlRecord with(SObjectField field, Object value) { + this.enhancedRecord.with(field, value); + return this; + } + + public DmlRecord withRelationship(SObjectField relationshipField, SObject relatedToRecord) { + this.withRelationship(relationshipField, new EnhancedRecord(relatedToRecord)); + return this; + } + + public DmlRecord withRelationship(SObjectField relationshipField, EnhancedRecord enhancedRelatedToRecord) { + this.enhancedRecord.withRelationship(new ParentRelationship(relationshipField, enhancedRelatedToRecord)); + return this; + } + + public DmlRecord withRelationship(SObjectField relationshipField, SObjectField externalIdField, Object externalId) { + this.enhancedRecord.withRelationship(new ExternalRelationship(relationshipField, externalIdField, externalId)); + return this; + } + + public EnhancedRecord get() { + return this.enhancedRecord; + } + + public SObjectType getSObjectType() { + return this.enhancedRecord.getSObjectType(); + } + } + + public class EnhancedRecord { + private SObject currentRecord; + private DmlStrategy strategy; + private Boolean hasTemporaryId = false; + + private List parentRelationships = new List(); + private List externalRelationships = new List(); + + private EnhancedRecord(SObject currentRecord) { + this.currentRecord = currentRecord; + } + + private EnhancedRecord(Id currentRecordId) { + this(currentRecordId?.getSObjectType()?.newSObject(currentRecordId)); + } + + private void with(SObjectField field, Object value) { + this.currentRecord.put(field, value); + } + + private void withRelationship(ParentRelationship parentRelationship) { + this.parentRelationships.add(parentRelationship); + } + + private void withRelationship(ExternalRelationship externalRelationship) { + this.externalRelationships.add(externalRelationship); + } + + private void setDatabaseStrategy(DmlStrategy strategy) { + this.strategy = strategy; + } + + private void addToDependencyGraph(OrderDependencyGraph orderDependencyGraph) { + orderDependencyGraph.ensureRecordIsTracked(this); + + for (ParentRelationship parentRelationship : this.parentRelationships) { + orderDependencyGraph.addDependency(parentRelationship.getRelatedToEnhancedRecord(), this); + } + } + + private void resolveRecordRelationships() { + for (ParentRelationship parentRelationship : this.parentRelationships) { + parentRelationship.resolve(this.currentRecord); + } + + for (ExternalRelationship externalRelationship : this.externalRelationships) { + externalRelationship.resolve(this.currentRecord); + } + } + + private void setTemporaryId() { + if (this.hasId()) { + return; + } + + this.hasTemporaryId = true; + this.currentRecord.Id = randomIdGenerator.get(this.getSObjectType()); + } + + private void clearTemporaryId() { + if (!this.hasTemporaryId) { + return; + } + + this.hasTemporaryId = false; + this.currentRecord.Id = null; + } + + private SObjectType getSObjectType() { + return this.currentRecord?.getSObjectType(); + } + + private SObject getRecord() { + return this.currentRecord; + } + + private Boolean hasId() { + return String.isNotBlank(this.getRecordId()); + } + + private Id getRecordId() { + return this.currentRecord?.Id; + } + } + + private class ParentRelationship { + private SObjectField relationshipField; + private EnhancedRecord relatedToEnhancedRecord; + + private ParentRelationship(SObjectField relationshipField, EnhancedRecord relatedToRecord) { + this.validateRelationshipField(relationshipField); + + this.relationshipField = relationshipField; + this.relatedToEnhancedRecord = relatedToRecord; + } + + private EnhancedRecord getRelatedToEnhancedRecord() { + return this.relatedToEnhancedRecord; + } + + private void resolve(SObject currentRecord) { + currentRecord.put(this.relationshipField, this.relatedToEnhancedRecord.getRecordId()); + } + + private void validateRelationshipField(SObjectField relationshipField) { + if (String.isBlank(relationshipField.getDescribe().getRelationshipName())) { + throw new DmlException('Invalid argument: ' + relationshipField.toString() + '. Field supplied is not a relationship field.'); + } + } + } + + private class ExternalRelationship { + private SObjectField relationshipField; + private SObjectField externalIdField; + private SObjectType relatedToType; + private Object relatedRecordExternalId; + + private ExternalRelationship(SObjectField relationshipField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId) { + this.validateRelatedToField(relationshipField); + this.validateExternalIdField(relationshipField, relatedObjectExternalIdField); + + this.relationshipField = relationshipField; + this.relatedToType = relationshipField.getDescribe().getReferenceTo()[0]; + this.externalIdField = relatedObjectExternalIdField; + this.relatedRecordExternalId = relatedRecordExternalId; + } + + private void resolve(SObject currentRecord) { + SObject relationshipObject = this.relatedToType.newSObject(); + relationshipObject.put(this.externalIdField.getDescribe().getName(), this.relatedRecordExternalId); + + currentRecord.putSObject(this.relationshipField.getDescribe().getRelationshipName(), relationshipObject); + } + + private void validateRelatedToField(SObjectField relationshipField) { + if (String.isBlank(relationshipField.getDescribe().getRelationshipName())) { + throw new DmlException('Invalid argument: relationshipField. Field supplied is not a relationship field.'); + } + } + + private void validateExternalIdField(SObjectField relationshipField, SObjectField externalIdField) { + if (!externalIdField.getDescribe().isExternalId()) { + throw new DmlException('Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.'); + } + + SObjectType relatedObjectType = relationshipField.getDescribe().getReferenceTo()[0]; + String externalIdFieldName = externalIdField.getDescribe().getName(); + + Boolean relatedHasExternalIdField = relatedObjectType.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); + + if (!relatedHasExternalIdField) { + throw new DmlException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); + } + } + } + + // Kahn's algorithm for topological sorting of records based on their dependencies + private class OrderDependencyGraph { + private Map> dependentRecordsById = new Map>(); + private Map prerequisiteCountById = new Map(); + + private void ensureRecordIsTracked(EnhancedRecord enhancedRecord) { + Id recordId = enhancedRecord.getRecordId(); + + if (!this.dependentRecordsById.containsKey(recordId)) { + this.dependentRecordsById.put(recordId, new Set()); + } + + if (!this.prerequisiteCountById.containsKey(recordId)) { + this.prerequisiteCountById.put(recordId, 0); + } + } + + private void addDependency(EnhancedRecord prerequisite, EnhancedRecord dependent) { + if (prerequisite.getRecordId() == dependent.getRecordId()) { + throw new DmlException('Self-dependency detected for record: ' + prerequisite.getRecordId()); + } + + if (prerequisite.getRecordId() == null) { + throw new DmlException('Relationship was registered for a record that has not been registered yet.'); + } + + this.ensureRecordIsTracked(prerequisite); + this.ensureRecordIsTracked(dependent); + + Id prerequisiteId = prerequisite.getRecordId(); + Id dependentId = dependent.getRecordId(); + + this.dependentRecordsById.get(prerequisiteId).add(dependentId); + this.prerequisiteCountById.put(dependentId, this.prerequisiteCountById.get(dependentId) + 1); + } + + private List> computeExecutionWaves() { + if (this.prerequisiteCountById.isEmpty()) { + return new List>(); + } + + Integer processedCount = 0; + List readyToProcessIds = this.findRecordsReadyToProcess(); + + List> executionWaves = new List>(); + + while (!readyToProcessIds.isEmpty()) { + List currentWave = readyToProcessIds; + executionWaves.add(currentWave); + + List nextWave = new List(); + + for (Id processedId : currentWave) { + processedCount++; - public List updates() { - return this.getOperationResults(OperationType.UPDATE_DML); - } + for (Id dependentId : (this.dependentRecordsById.get(processedId) ?? new Set())) { + Integer remainingPrerequisites = this.prerequisiteCountById.get(dependentId) - 1; + this.prerequisiteCountById.put(dependentId, remainingPrerequisites); - public List upserts() { - return this.getOperationResults(OperationType.UPSERT_DML); - } + if (remainingPrerequisites == 0) { + nextWave.add(dependentId); + } + } + } - public List deletes() { - return this.getOperationResults(OperationType.DELETE_DML); - } - - public List undeletes() { - return this.getOperationResults(OperationType.UNDELETE_DML); - } - - public List merges() { - return this.getOperationResults(OperationType.MERGE_DML); - } - - public List events() { - return this.getOperationResults(OperationType.PUBLISH_DML); - } - - public OperationResult insertsOf(SObjectType objectType) { - return this.getOperationResult(OperationType.INSERT_DML, objectType); - } - - public OperationResult updatesOf(SObjectType objectType) { - return this.getOperationResult(OperationType.UPDATE_DML, objectType); - } - - public OperationResult upsertsOf(SObjectType objectType) { - return this.getOperationResult(OperationType.UPSERT_DML, objectType); - } - - public OperationResult deletesOf(SObjectType objectType) { - return this.getOperationResult(OperationType.DELETE_DML, objectType); - } - - public OperationResult undeletesOf(SObjectType objectType) { - return this.getOperationResult(OperationType.UNDELETE_DML, objectType); - } - - public OperationResult mergesOf(SObjectType objectType) { - return this.getOperationResult(OperationType.MERGE_DML, objectType); - } - - public OperationResult eventsOf(SObjectType objectType) { - return this.getOperationResult(OperationType.PUBLISH_DML, objectType); - } - - private List getOperationResults(OperationType operationType) { - return this.operationResultsByObjectType.get(operationType).values(); - } - - private OperationResult getOperationResult(OperationType operationType, SObjectType objectType) { - return this.operationResultsByObjectType.get(operationType).get(objectType) ?? new SObjectAggregatedResult(operationType, objectType); - } - - public List all() { - List allResults = new List(); - - for (OperationType operationType : this.operationResultsByObjectType.keySet()) { - allResults.addAll(this.operationResultsByObjectType.get(operationType).values()); - } - - return allResults; - } - } - - private class Configuration { - private System.AccessLevel accessMode = System.AccessLevel.USER_MODE; - private Database.DmlOptions options = new Database.DmlOptions(); - private DmlSharing sharingExecutor = new InheritedSharing(); - private DuplicateCombineStrategy duplicateCombineStrategy = new ThrownExceptionDuplicateStrategy(); - private Boolean allOrNone = true; - private String dmlIdentifier = null; - - private Configuration() { - this.options.optAllOrNone = true; - } - - private void accessMode(System.AccessLevel accessMode) { - this.accessMode = accessMode; - } - - private void withSharing() { - this.sharingExecutor = new WithSharing(); - } - - private void withoutSharing() { - this.sharingExecutor = new WithoutSharing(); - } - - private void identifier(String dmlIdentifier) { - this.dmlIdentifier = dmlIdentifier; - } - - private void allowPartialSuccess() { - this.allOrNone = false; - this.options.optAllOrNone = false; - } - - private void combineOnDuplicate() { - this.duplicateCombineStrategy = new MergeDuplicateStrategy(); - } - - private void options(Database.DmlOptions options) { - this.options = options; - this.options.optAllOrNone = this.options.optAllOrNone ?? this.allOrNone ?? true; - } - - private void preview() { - System.debug( - LoggingLevel.ERROR, - '\n\n============ DML Configuration ============' + - '\nOptions: ' + - JSON.serializePretty(this.options) + - '\nSharing Executor: ' + - String.valueOf(this.sharingExecutor).split(':')[0] + - '\nAll Or None: ' + - this.allOrNone + - '\nDML Identifier: ' + - this.dmlIdentifier + - '\n=======================================\n' - ); - } - - private void skipDuplicateRules() { - this.options.duplicateRuleHeader.allowSave = true; - } - } - - private abstract class DmlStrategy { - private SObjectType objectType; - private Configuration globalConfiguration; - - protected DmlStrategy(SObjectType objectType, Configuration configuration) { - this.objectType = objectType; - this.globalConfiguration = configuration; - } - - private SObjectType getObjectType() { - return this.objectType; - } - - private OperationResult commitWork(List records) { - return this.globalConfiguration.sharingExecutor.execute(this, records); - } - - private OperationResult execute(Records records) { - List enhancedRecords = new List(); - - for (Record record : records.get()) { - enhancedRecords.add(record.get()); - } - - return this.execute(enhancedRecords); - } - - private OperationResult execute(List records) { - List recordsToProcess = this.getRecordsToProcess(records); - - ExecutionResult result = new ExecutionResult(this.getOperationType(), this.objectType).setRecords(recordsToProcess); - - DmlMock dmlMock = dmlIdentifierToMock.get(this.globalConfiguration.dmlIdentifier); - - if (dmlMock != null && dmlMock.shouldBeMocked(this.getOperationType(), this.objectType)) { - return result.setRecordResults(dmlMock.getMockedRecordResults(this, recordsToProcess)); - } - - return result.setRecordResults(this.getResultAdapter().get(this.executeDml(recordsToProcess), recordsToProcess)); - } - - private List getRecordsToProcess(List records) { - List recordsToProcess = new List(); - - for (EnhancedRecord enhancedRecord : records) { - enhancedRecord.resolveRecordRelationships(); - recordsToProcess.add(enhancedRecord.getRecord()); - } - - return recordsToProcess; - } - - protected abstract OperationType getOperationType(); - protected abstract DmlResultAdapter getResultAdapter(); - protected abstract List executeDml(List recordsToProcess); - protected abstract RecordSummary executeMockedDml(SObject record); - - protected virtual void validate(EnhancedRecord enhancedRecord) { - return; - } - - protected virtual String operationId() { - return this.getOperationType().name() + '_' + (this.getObjectType()?.toString() ?? 'EMPTY'); - } - } - - // Strategies - - private inherited sharing class InsertStrategy extends DmlStrategy { - private InsertStrategy(SObjectType objectType, Configuration configuration) { - super(objectType, configuration); - } - - protected override OperationType getOperationType() { - return OperationType.INSERT_DML; - } - - protected override DmlResultAdapter getResultAdapter() { - return new SaveResultAdapter(); - } - - protected override void validate(EnhancedRecord enhancedRecord) { - if (enhancedRecord.hasId()) { - throw new DmlException('Only new records can be registered as new.'); - } - } - - protected override List executeDml(List recordsToProcess) { - return Database.insert(recordsToProcess, this.globalConfiguration.options, this.globalConfiguration.accessMode); - } - - protected override RecordSummary executeMockedDml(SObject record) { - record.put('Id', randomIdGenerator.get(record.getSObjectType())); - return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); - } - } - - private inherited sharing class UpsertStrategy extends DmlStrategy { - private SObjectField externalIdField; - - private UpsertStrategy(SObjectType objectType, Configuration configuration) { - super(objectType, configuration); - } - - private UpsertStrategy withExternalIdField(SObjectField externalIdField) { - this.externalIdField = externalIdField; - return this; - } - - protected override String operationId() { - String externalId = this.externalIdField == null ? 'Id' : this.externalIdField.toString(); - return super.operationId() + '_' + externalId; - } - - protected override OperationType getOperationType() { - return OperationType.UPSERT_DML; - } - - protected override DmlResultAdapter getResultAdapter() { - return new UpsertResultAdapter(); - } - - protected override List executeDml(List recordsToProcess) { - if (this.externalIdField == null) { - return Database.upsert(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); - } - - return Database.upsert(recordsToProcess, this.externalIdField, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); - } - - protected override RecordSummary executeMockedDml(SObject record) { - if (record.Id == null) { - record.put('Id', randomIdGenerator.get(record.getSObjectType())); - } - return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); - } - } - - private inherited sharing class UpdateStrategy extends DmlStrategy { - private UpdateStrategy(SObjectType objectType, Configuration configuration) { - super(objectType, configuration); - } - - protected override OperationType getOperationType() { - return OperationType.UPDATE_DML; - } - - protected override DmlResultAdapter getResultAdapter() { - return new SaveResultAdapter(); - } - - protected override void validate(EnhancedRecord enhancedRecord) { - if (!enhancedRecord.hasId()) { - throw new DmlException('Only existing records can be updated.'); - } - } - - protected override List executeDml(List recordsToProcess) { - return Database.update(recordsToProcess, this.globalConfiguration.options, this.globalConfiguration.accessMode); - } - - protected override RecordSummary executeMockedDml(SObject record) { - return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); - } - } - - private inherited sharing class MergeStrategy extends DmlStrategy { - private EnhancedRecord mergeToRecord; - - private MergeStrategy(SObjectType objectType, Configuration configuration, Record mergeToRecord) { - super(objectType, configuration); - this.validate(mergeToRecord.get()); - this.mergeToRecord = mergeToRecord.get(); - } - - protected override String operationId() { - return super.operationId() + '_' + this.mergeToRecord.getRecordId(); - } - - protected override OperationType getOperationType() { - return OperationType.MERGE_DML; - } - - protected override DmlResultAdapter getResultAdapter() { - return new MergeResultAdapter(); - } - - protected override void validate(EnhancedRecord enhancedRecord) { - if (!enhancedRecord.hasId()) { - throw new DmlException('Only existing records can be merged.'); - } - } - - protected override List executeDml(List recordsToProcess) { - return Database.merge(this.mergeToRecord.getRecord(), recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); - } - - protected override RecordSummary executeMockedDml(SObject record) { - return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); - } - } - - private inherited sharing class DeleteStrategy extends DmlStrategy { - private Boolean makeHardDelete = false; - - private DeleteStrategy(SObjectType objectType, Configuration configuration) { - super(objectType, configuration); - } - - private DeleteStrategy withHardDelete() { - this.makeHardDelete = true; - return this; - } - - protected override String operationId() { - return super.operationId() + '_' + this.makeHardDelete; - } - - protected override OperationType getOperationType() { - return OperationType.DELETE_DML; - } - - protected override DmlResultAdapter getResultAdapter() { - return new DeleteResultAdapter(); - } - - protected override void validate(EnhancedRecord enhancedRecord) { - if (!enhancedRecord.hasId()) { - throw new DmlException('Only existing records can be registered as deleted.'); - } - } - - protected override List executeDml(List recordsToProcess) { - List dmlResults = Database.delete(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); - - if (this.makeHardDelete) { - Database.emptyRecycleBin(recordsToProcess); - } - - return dmlResults; - } - - protected override RecordSummary executeMockedDml(SObject record) { - return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); - } - } - - private inherited sharing class UndeleteStrategy extends DmlStrategy { - private UndeleteStrategy(SObjectType objectType, Configuration configuration) { - super(objectType, configuration); - } - - protected override OperationType getOperationType() { - return OperationType.UNDELETE_DML; - } - - protected override DmlResultAdapter getResultAdapter() { - return new UndeleteResultAdapter(); - } - - protected override void validate(EnhancedRecord enhancedRecord) { - if (!enhancedRecord.hasId()) { - throw new DmlException('Only deleted records can be undeleted.'); - } - } - - protected override List executeDml(List recordsToProcess) { - return Database.undelete(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); - } - - protected override RecordSummary executeMockedDml(SObject record) { - return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); - } - } - - private inherited sharing class PlatformEventStrategy extends DmlStrategy { - private PlatformEventStrategy(SObjectType objectType, Configuration configuration) { - super(objectType, configuration); - } - - protected override OperationType getOperationType() { - return OperationType.PUBLISH_DML; - } - - protected override DmlResultAdapter getResultAdapter() { - return new SaveResultAdapter(); - } - - protected override List executeDml(List recordsToProcess) { - return EventBus.publish(recordsToProcess); - } - - protected override RecordSummary executeMockedDml(SObject record) { - return new RecordSummary().isSuccess(true).recordId(randomIdGenerator.get(record.getSObjectType())).record(record); - } - } - - // Sharing Executors - - private interface DmlSharing { - OperationResult execute(DmlStrategy strategy, List records); - } - - private inherited sharing class InheritedSharing implements DmlSharing { - public OperationResult execute(DmlStrategy strategy, List records) { - return strategy.execute(records); - } - } - - private without sharing class WithoutSharing implements DmlSharing { - public OperationResult execute(DmlStrategy strategy, List records) { - return strategy.execute(records); - } - } - - private with sharing class WithSharing implements DmlSharing { - public OperationResult execute(DmlStrategy strategy, List records) { - return strategy.execute(records); - } - } - - // Result Adapters - - private abstract class DmlResultAdapter { - public List get(List dmlResults, List processedRecords) { - List recordResults = new List(); - - for (Integer i = 0; i < dmlResults.size(); i++) { - recordResults.add(this.transform(dmlResults[i]).record(processedRecords[i])); - } - - return recordResults; - } - - protected abstract RecordSummary transform(Object result); - } - - private class SaveResultAdapter extends DmlResultAdapter { - public override RecordSummary transform(Object result) { - Database.SaveResult saveResult = (Database.SaveResult) result; - - return new RecordSummary().isSuccess(saveResult.isSuccess()).recordId(saveResult.getId()).errors(saveResult.getErrors()); - } - } - - private class UpsertResultAdapter extends DmlResultAdapter { - public override RecordSummary transform(Object result) { - Database.UpsertResult upsertResult = (Database.UpsertResult) result; - - return new RecordSummary().isSuccess(upsertResult.isSuccess()).recordId(upsertResult.getId()).errors(upsertResult.getErrors()); - } - } - - private class MergeResultAdapter extends DmlResultAdapter { - public override RecordSummary transform(Object result) { - Database.MergeResult mergeResult = (Database.MergeResult) result; - - return new RecordSummary().isSuccess(mergeResult.isSuccess()).recordId(mergeResult.getId()).errors(mergeResult.getErrors()); - } - } - - private class DeleteResultAdapter extends DmlResultAdapter { - public override RecordSummary transform(Object result) { - Database.DeleteResult deleteResult = (Database.DeleteResult) result; - - return new RecordSummary().isSuccess(deleteResult.isSuccess()).recordId(deleteResult.getId()).errors(deleteResult.getErrors()); - } - } - - private class UndeleteResultAdapter extends DmlResultAdapter { - public override RecordSummary transform(Object result) { - Database.UndeleteResult undeleteResult = (Database.UndeleteResult) result; - - return new RecordSummary().isSuccess(undeleteResult.isSuccess()).recordId(undeleteResult.getId()).errors(undeleteResult.getErrors()); - } - } - - private interface DuplicateCombineStrategy { - void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord); - } - - private class ThrownExceptionDuplicateStrategy implements DuplicateCombineStrategy { - public void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord) { - throw new DmlException('Duplicate records found during registration. Fix the code or use the combineOnDuplicate() method.'); - } - } - - private class MergeDuplicateStrategy implements DuplicateCombineStrategy { - public void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord) { - SObject existingRecord = mergeToRecord.getRecord(); - SObject newRecord = duplicateRecord.getRecord(); - - for (String field : newRecord.getPopulatedFieldsAsMap().keySet()) { - existingRecord.put(field, newRecord.get(field)); - } - } - } - - private abstract class DmlOperationResult implements OperationResult { - private OperationType type; - private SObjectType objectType; - private List recordResults = new List(); - private List records = new List(); - - private Boolean isResultsCached = false; - private List cachedSuccesses = new List(); - private List cachedFailures = new List(); - private List cachedErrors = new List(); - - private DmlOperationResult(OperationType type, SObjectType objectType) { - this.type = type; - this.objectType = objectType; - } - - public SObjectType objectType() { - return this.objectType; - } - - public OperationType operationType() { - return this.type; - } - - public Boolean hasFailures() { - return !this.failures().isEmpty(); - } - - public List errors() { - this.cacheResults(); - return this.cachedErrors; - } - - public List records() { - return this.records; - } - - public List successes() { - this.cacheResults(); - return this.cachedSuccesses; - } - - public List failures() { - this.cacheResults(); - return this.cachedFailures; - } - - public List recordResults() { - return this.recordResults; - } - - private void cacheResults() { - if (this.isResultsCached) { - return; - } - - for (RecordResult recordResult : this.recordResults) { - if (recordResult.isSuccess()) { - this.cachedSuccesses.add(recordResult.record()); - continue; - } - - this.cachedErrors.addAll(recordResult.errors()); - this.cachedFailures.add(recordResult.record()); - } - - this.isResultsCached = true; - } - } - - private class ExecutionResult extends DmlOperationResult { - private ExecutionResult(OperationType type, SObjectType objectType) { - super(type, objectType); - } - - private ExecutionResult setRecords(List records) { - this.records = records; - return this; - } - - private ExecutionResult setRecordResults(List recordResults) { - this.recordResults = recordResults; - return this; - } - } - - private class SObjectAggregatedResult extends DmlOperationResult { - private SObjectAggregatedResult(OperationType type, SObjectType objectType) { - super(type, objectType); - } - - private SObjectAggregatedResult add(OperationResult recordResult) { - this.recordResults.addAll(recordResult.recordResults()); - this.records.addAll(recordResult.records()); - return this; - } - } - - private class RecordSummary implements RecordResult { - private Id recordId; - private SObject record; - private Boolean isSuccess = false; - private List errors = new List(); - - public Id id() { - return this.recordId; - } - - public SObject record() { - return this.record; - } - - public Boolean isSuccess() { - return this.isSuccess; - } - - public List errors() { - return this.errors; - } - - private RecordSummary recordId(Id recordId) { - this.recordId = recordId; - return this; - } - - private RecordSummary record(SObject record) { - this.record = record; - return this; - } - - @SuppressWarnings('PMD.AvoidBooleanMethodParameters') - private RecordSummary isSuccess(Boolean isSuccess) { - this.isSuccess = isSuccess; - return this; - } - - private RecordSummary error(Error error) { - this.errors.add(error); - return this; - } - - private RecordSummary errors(List errors) { - for (Database.Error error : errors ?? new List()) { - this.errors.add(new RecordProcessingError(error)); - } - return this; - } - } - - private class RecordProcessingError implements Error { - private String message; - private System.StatusCode statusCode; - private List fields; - - private RecordProcessingError() { - // Default constructor for builder pattern - } - - private RecordProcessingError(Database.Error error) { - this.message = error.getMessage(); - this.statusCode = error.getStatusCode(); - this.fields = error.getFields(); - } - - private RecordProcessingError setMessage(String message) { - this.message = message; - return this; - } - - private RecordProcessingError setStatusCode(System.StatusCode statusCode) { - this.statusCode = statusCode; - return this; - } - - private RecordProcessingError setFields(List fields) { - this.fields = fields; - return this; - } - - public String message() { - return this.message; - } - - public System.StatusCode statusCode() { - return this.statusCode; - } - - public List fields() { - return this.fields; - } - } - - private class DmlMock implements Mockable { - private Set mockedDmlTypes = new Set(); - private Set thrownExceptionDmlTypes = new Set(); - - private Map> mockedObjectTypesByDmlType = new Map>(); - private Map> thrownExceptionDmlTypesByObjectTypes = new Map>(); - - public Mockable allDmls() { - this.mockedDmlTypes.addAll(OperationType.values()); - return this; - } - - public Mockable allInserts() { - return this.thenMockDml(OperationType.INSERT_DML); - } - - public Mockable allUpdates() { - return this.thenMockDml(OperationType.UPDATE_DML); - } - - public Mockable allUpserts() { - return this.thenMockDml(OperationType.UPSERT_DML); - } - - public Mockable allDeletes() { - return this.thenMockDml(OperationType.DELETE_DML); - } - - public Mockable allUndeletes() { - return this.thenMockDml(OperationType.UNDELETE_DML); - } - - public Mockable allMerges() { - return this.thenMockDml(OperationType.MERGE_DML); - } - - public Mockable allPublishes() { - return this.thenMockDml(OperationType.PUBLISH_DML); - } - - private Mockable thenMockDml(OperationType dmlType) { - this.mockedDmlTypes.add(dmlType); - return this; - } - - public Mockable insertsFor(SObjectType objectType) { - return this.thenMockDmlFor(OperationType.INSERT_DML, objectType); - } - - public Mockable updatesFor(SObjectType objectType) { - return this.thenMockDmlFor(OperationType.UPDATE_DML, objectType); - } - - public Mockable upsertsFor(SObjectType objectType) { - return this.thenMockDmlFor(OperationType.UPSERT_DML, objectType); - } - - public Mockable deletesFor(SObjectType objectType) { - return this.thenMockDmlFor(OperationType.DELETE_DML, objectType); - } - - public Mockable undeletesFor(SObjectType objectType) { - return this.thenMockDmlFor(OperationType.UNDELETE_DML, objectType); - } - - public Mockable mergesFor(SObjectType objectType) { - return this.thenMockDmlFor(OperationType.MERGE_DML, objectType); - } - - public Mockable publishesFor(SObjectType objectType) { - return this.thenMockDmlFor(OperationType.PUBLISH_DML, objectType); - } - - private Mockable thenMockDmlFor(OperationType dmlType, SObjectType objectType) { - if (!this.mockedObjectTypesByDmlType.containsKey(dmlType)) { - this.mockedObjectTypesByDmlType.put(dmlType, new Set()); - } - this.mockedObjectTypesByDmlType.get(dmlType).add(objectType); - return this; - } - - public Mockable exceptionOnInserts() { - return this.thenExceptionOn(OperationType.INSERT_DML); - } - - public Mockable exceptionOnUpdates() { - return this.thenExceptionOn(OperationType.UPDATE_DML); - } - - public Mockable exceptionOnUpserts() { - return this.thenExceptionOn(OperationType.UPSERT_DML); - } - - public Mockable exceptionOnDeletes() { - return this.thenExceptionOn(OperationType.DELETE_DML); - } - - public Mockable exceptionOnUndeletes() { - return this.thenExceptionOn(OperationType.UNDELETE_DML); - } - - public Mockable exceptionOnMerges() { - return this.thenExceptionOn(OperationType.MERGE_DML); - } - - public Mockable exceptionOnPublishes() { - return this.thenExceptionOn(OperationType.PUBLISH_DML); - } - - private Mockable thenExceptionOn(OperationType dmlType) { - this.thrownExceptionDmlTypes.add(dmlType); - return this; - } - - public Mockable exceptionOnInsertsFor(SObjectType objectType) { - return this.thenExceptionOnFor(OperationType.INSERT_DML, objectType); - } - - public Mockable exceptionOnUpdatesFor(SObjectType objectType) { - return this.thenExceptionOnFor(OperationType.UPDATE_DML, objectType); - } - - public Mockable exceptionOnUpsertsFor(SObjectType objectType) { - return this.thenExceptionOnFor(OperationType.UPSERT_DML, objectType); - } - - public Mockable exceptionOnDeletesFor(SObjectType objectType) { - return this.thenExceptionOnFor(OperationType.DELETE_DML, objectType); - } - - public Mockable exceptionOnUndeletesFor(SObjectType objectType) { - return this.thenExceptionOnFor(OperationType.UNDELETE_DML, objectType); - } - - public Mockable exceptionOnMergesFor(SObjectType objectType) { - return this.thenExceptionOnFor(OperationType.MERGE_DML, objectType); - } - - public Mockable exceptionOnPublishesFor(SObjectType objectType) { - return this.thenExceptionOnFor(OperationType.PUBLISH_DML, objectType); - } - - private Mockable thenExceptionOnFor(OperationType dmlType, SObjectType objectType) { - if (!this.thrownExceptionDmlTypesByObjectTypes.containsKey(dmlType)) { - this.thrownExceptionDmlTypesByObjectTypes.put(dmlType, new Set()); - } - this.thrownExceptionDmlTypesByObjectTypes.get(dmlType).add(objectType); - return this; - } - - private Boolean shouldBeMocked(OperationType dmlType, SObjectType objectType) { - return this.mockedDmlTypes.contains(dmlType) || - (this.mockedObjectTypesByDmlType.get(dmlType) ?? new Set()).contains(objectType) || - this.shouldThrowException(dmlType, objectType); - } - - private List getMockedRecordResults(DmlStrategy strategy, List recordsToProcess) { - if (this.shouldThrowException(strategy.getOperationType(), strategy.getObjectType())) { - if (strategy.globalConfiguration.allOrNone) { - throw new DmlException('Exception thrown for ' + strategy.getOperationType() + ' operation.'); - } - - // all or none is false, so we need to return a list of record results with errors - return this.getMockedRecordErrors(strategy, recordsToProcess); - } - - return this.getMockedRecordSuccesses(strategy, recordsToProcess); - } - - private List getMockedRecordErrors(DmlStrategy strategy, List recordsToProcess) { - List recordResults = new List(); - String errorMessage = 'Exception thrown for ' + strategy.getOperationType() + ' operation.'; - - for (SObject record : recordsToProcess) { - RecordSummary recordSummary = new RecordSummary() - .isSuccess(false) - .error(new RecordProcessingError().setMessage(errorMessage).setStatusCode(System.StatusCode.ALREADY_IN_PROCESS).setFields(new List{ 'Id' })) - .record(record); - - recordResults.add(recordSummary); - } - - return recordResults; - } - - private List getMockedRecordSuccesses(DmlStrategy strategy, List recordsToProcess) { - List recordResults = new List(); - - for (SObject record : recordsToProcess) { - recordResults.add(strategy.executeMockedDml(record)); - } - - return recordResults; - } - - private Boolean shouldThrowException(OperationType dmlType, SObjectType objectType) { - return this.thrownExceptionDmlTypes.contains(dmlType) || (this.thrownExceptionDmlTypesByObjectTypes.get(dmlType) ?? new Set()).contains(objectType); - } - } - - private class DmlRecords implements Records { - private List records = new List(); - private SObjectType objectType; - - private Map valueByFieldApiName = new Map(); - private List parentRelationships = new List(); - private List externalRelationships = new List(); - - private DmlRecords(List records) { - this.objectType = this.resolveObjectType(records); - - for (SObject record : records) { - this.records.add(new DmlRecord(record)); - } - } - - private SObjectType resolveObjectType(List records) { - if(records == null) { - return null; - } - - SObjectType resolvedType = null; - - for (SObject record : records) { - if (record == null) { - continue; - } - - SObjectType currentType = record.getSObjectType(); - if (resolvedType == null) { - resolvedType = currentType; - continue; - } - - if (resolvedType != currentType) { - throw new DmlException('Mixed SObject types in a single List operation are not supported.'); - } - } - - return resolvedType; - } - - private DmlRecords(Iterable recordIds) { - this.setObjectTypeBasedOnIds(recordIds); - - for (Id recordId : recordIds) { - this.records.add(new DmlRecord(recordId)); - } - } - - private void setObjectTypeBasedOnIds(Iterable recordIds) { - Iterator it = recordIds.iterator(); - if (it.hasNext()) { - this.objectType = it.next().getSObjectType(); - } - } - - public DmlRecords with(SObjectField field, Object value) { - this.valueByFieldApiName.put(field, value); - return this; - } - - public DmlRecords withRelationship(SObjectField relationshipField, SObject relatedToRecord) { - this.parentRelationships.add(new ParentRelationship(relationshipField, new EnhancedRecord(relatedToRecord))); - return this; - } - - public DmlRecords withRelationship(SObjectField relationshipField, SObjectField externalIdField, Object externalId) { - this.externalRelationships.add(new ExternalRelationship(relationshipField, externalIdField, externalId)); - return this; - } - - public List get() { - for (Record record : this.records) { - EnhancedRecord enhancedRecord = record.get(); - - this.populateFieldsValues(enhancedRecord); - this.populateParentRelationships(enhancedRecord); - this.populateExternalRelationships(enhancedRecord); - } - - return this.records; - } - - public SObjectType getSObjectType() { - return this.objectType; - } - - private void populateFieldsValues(EnhancedRecord enhancedRecord) { - for (SObjectField field : this.valueByFieldApiName.keySet()) { - enhancedRecord.with(field, this.valueByFieldApiName.get(field)); - } - } - - private void populateParentRelationships(EnhancedRecord enhancedRecord) { - for (ParentRelationship parentRelationship : this.parentRelationships) { - enhancedRecord.withRelationship(parentRelationship); - } - } - - private void populateExternalRelationships(EnhancedRecord enhancedRecord) { - for (ExternalRelationship externalRelationship : this.externalRelationships) { - enhancedRecord.withRelationship(externalRelationship); - } - } - } - - private class DmlRecord implements Record { - private EnhancedRecord enhancedRecord; - - private DmlRecord(SObject record) { - this.enhancedRecord = new EnhancedRecord(record); - } - - private DmlRecord(Id recordId) { - if (recordId == null) { - throw new DmlException('Invalid argument: recordId. Record ID cannot be null.'); - } - - this.enhancedRecord = new EnhancedRecord(recordId); - } - - public DmlRecord with(SObjectField field, Object value) { - this.enhancedRecord.with(field, value); - return this; - } - - public DmlRecord withRelationship(SObjectField relationshipField, SObject relatedToRecord) { - this.withRelationship(relationshipField, new EnhancedRecord(relatedToRecord)); - return this; - } - - public DmlRecord withRelationship(SObjectField relationshipField, EnhancedRecord enhancedRelatedToRecord) { - this.enhancedRecord.withRelationship(new ParentRelationship(relationshipField, enhancedRelatedToRecord)); - return this; - } - - public DmlRecord withRelationship(SObjectField relationshipField, SObjectField externalIdField, Object externalId) { - this.enhancedRecord.withRelationship(new ExternalRelationship(relationshipField, externalIdField, externalId)); - return this; - } - - public EnhancedRecord get() { - return this.enhancedRecord; - } - - public SObjectType getSObjectType() { - return this.enhancedRecord.getSObjectType(); - } - } - - global class EnhancedRecord { - private SObject currentRecord; - private DmlStrategy strategy; - private Boolean hasTemporaryId = false; - - private List parentRelationships = new List(); - private List externalRelationships = new List(); - - private EnhancedRecord(SObject currentRecord) { - this.currentRecord = currentRecord; - } - - private EnhancedRecord(Id currentRecordId) { - this(currentRecordId?.getSObjectType()?.newSObject(currentRecordId)); - } - - private void with(SObjectField field, Object value) { - this.currentRecord.put(field, value); - } - - private void withRelationship(ParentRelationship parentRelationship) { - this.parentRelationships.add(parentRelationship); - } - - private void withRelationship(ExternalRelationship externalRelationship) { - this.externalRelationships.add(externalRelationship); - } - - private void setDatabaseStrategy(DmlStrategy strategy) { - this.strategy = strategy; - } - - private void addToDependencyGraph(OrderDependencyGraph orderDependencyGraph) { - orderDependencyGraph.ensureRecordIsTracked(this); - - for (ParentRelationship parentRelationship : this.parentRelationships) { - orderDependencyGraph.addDependency(parentRelationship.getRelatedToEnhancedRecord(), this); - } - } - - private void resolveRecordRelationships() { - for (ParentRelationship parentRelationship : this.parentRelationships) { - parentRelationship.resolve(this.currentRecord); - } - - for (ExternalRelationship externalRelationship : this.externalRelationships) { - externalRelationship.resolve(this.currentRecord); - } - } - - private void setTemporaryId() { - if (this.hasId()) { - return; - } - - this.hasTemporaryId = true; - this.currentRecord.Id = randomIdGenerator.get(this.getSObjectType()); - } - - private void clearTemporaryId() { - if (!this.hasTemporaryId) { - return; - } - - this.hasTemporaryId = false; - this.currentRecord.Id = null; - } - - private SObjectType getSObjectType() { - return this.currentRecord?.getSObjectType(); - } - - private SObject getRecord() { - return this.currentRecord; - } - - private Boolean hasId() { - return String.isNotBlank(this.getRecordId()); - } - - private Id getRecordId() { - return this.currentRecord?.Id; - } - } - - private class ParentRelationship { - private SObjectField relationshipField; - private EnhancedRecord relatedToEnhancedRecord; - - private ParentRelationship(SObjectField relationshipField, EnhancedRecord relatedToRecord) { - this.validateRelationshipField(relationshipField); - - this.relationshipField = relationshipField; - this.relatedToEnhancedRecord = relatedToRecord; - } - - private EnhancedRecord getRelatedToEnhancedRecord() { - return this.relatedToEnhancedRecord; - } - - private void resolve(SObject currentRecord) { - currentRecord.put(this.relationshipField, this.relatedToEnhancedRecord.getRecordId()); - } - - private void validateRelationshipField(SObjectField relationshipField) { - if (String.isBlank(relationshipField.getDescribe().getRelationshipName())) { - throw new DmlException('Invalid argument: ' + relationshipField.toString() + '. Field supplied is not a relationship field.'); - } - } - } - - private class ExternalRelationship { - private SObjectField relationshipField; - private SObjectField externalIdField; - private SObjectType relatedToType; - private Object relatedRecordExternalId; - - private ExternalRelationship(SObjectField relationshipField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId) { - this.validateRelatedToField(relationshipField); - this.validateExternalIdField(relationshipField, relatedObjectExternalIdField); - - this.relationshipField = relationshipField; - this.relatedToType = relationshipField.getDescribe().getReferenceTo()[0]; - this.externalIdField = relatedObjectExternalIdField; - this.relatedRecordExternalId = relatedRecordExternalId; - } - - private void resolve(SObject currentRecord) { - SObject relationshipObject = this.relatedToType.newSObject(); - relationshipObject.put(this.externalIdField.getDescribe().getName(), this.relatedRecordExternalId); - - currentRecord.putSObject(this.relationshipField.getDescribe().getRelationshipName(), relationshipObject); - } - - private void validateRelatedToField(SObjectField relationshipField) { - if (String.isBlank(relationshipField.getDescribe().getRelationshipName())) { - throw new DmlException('Invalid argument: relationshipField. Field supplied is not a relationship field.'); - } - } - - private void validateExternalIdField(SObjectField relationshipField, SObjectField externalIdField) { - if (!externalIdField.getDescribe().isExternalId()) { - throw new DmlException('Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.'); - } - - SObjectType relatedObjectType = relationshipField.getDescribe().getReferenceTo()[0]; - String externalIdFieldName = externalIdField.getDescribe().getName(); - - Boolean relatedHasExternalIdField = relatedObjectType.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); - - if (!relatedHasExternalIdField) { - throw new DmlException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); - } - } - } - - // Kahn's algorithm for topological sorting of records based on their dependencies - private class OrderDependencyGraph { - private Map> dependentRecordsById = new Map>(); - private Map prerequisiteCountById = new Map(); - - private void ensureRecordIsTracked(EnhancedRecord enhancedRecord) { - Id recordId = enhancedRecord.getRecordId(); - - if (!this.dependentRecordsById.containsKey(recordId)) { - this.dependentRecordsById.put(recordId, new Set()); - } - - if (!this.prerequisiteCountById.containsKey(recordId)) { - this.prerequisiteCountById.put(recordId, 0); - } - } - - private void addDependency(EnhancedRecord prerequisite, EnhancedRecord dependent) { - if (prerequisite.getRecordId() == dependent.getRecordId()) { - throw new DmlException('Self-dependency detected for record: ' + prerequisite.getRecordId()); - } - - if (prerequisite.getRecordId() == null) { - throw new DmlException('Relationship was registered for a record that has not been registered yet.'); - } - - this.ensureRecordIsTracked(prerequisite); - this.ensureRecordIsTracked(dependent); - - Id prerequisiteId = prerequisite.getRecordId(); - Id dependentId = dependent.getRecordId(); - - this.dependentRecordsById.get(prerequisiteId).add(dependentId); - this.prerequisiteCountById.put(dependentId, this.prerequisiteCountById.get(dependentId) + 1); - } - - private List> computeExecutionWaves() { - if (this.prerequisiteCountById.isEmpty()) { - return new List>(); - } - - Integer processedCount = 0; - List readyToProcessIds = this.findRecordsReadyToProcess(); - - List> executionWaves = new List>(); - - while (!readyToProcessIds.isEmpty()) { - List currentWave = readyToProcessIds; - executionWaves.add(currentWave); - - List nextWave = new List(); - - for (Id processedId : currentWave) { - processedCount++; + readyToProcessIds = nextWave; + } - for (Id dependentId : (this.dependentRecordsById.get(processedId) ?? new Set())) { - Integer remainingPrerequisites = this.prerequisiteCountById.get(dependentId) - 1; - this.prerequisiteCountById.put(dependentId, remainingPrerequisites); + if (processedCount != this.prerequisiteCountById.size()) { + throw new DmlException('Cyclic dependencies detected among records.'); + } - if (remainingPrerequisites == 0) { - nextWave.add(dependentId); - } - } - } + return executionWaves; + } - readyToProcessIds = nextWave; - } + private List findRecordsReadyToProcess() { + List readyToProcess = new List(); - if (processedCount != this.prerequisiteCountById.size()) { - throw new DmlException('Cyclic dependencies detected among records.'); - } + for (Id recordId : this.prerequisiteCountById.keySet()) { + if (this.prerequisiteCountById.get(recordId) == 0) { + readyToProcess.add(recordId); + } + } - return executionWaves; - } + return readyToProcess; + } + } - private List findRecordsReadyToProcess() { - List readyToProcess = new List(); + @TestVisible + private class RandomIdGenerator { + private Integer counter = 0; + private Map prefixBySObject = new Map(); - for (Id recordId : this.prerequisiteCountById.keySet()) { - if (this.prerequisiteCountById.get(recordId) == 0) { - readyToProcess.add(recordId); - } - } + public Id get(SObjectType objectType) { + return this.get(this.getPrefix(objectType)); + } - return readyToProcess; - } - } + private Id get(String prefix) { + this.counter++; + return Id.valueOf(prefix + '0'.repeat(15 - prefix.length() - String.valueOf(this.counter).length()) + String.valueOf(this.counter)); + } - @TestVisible - private class RandomIdGenerator { - private Integer counter = 0; - private Map prefixBySObject = new Map(); + private String getPrefix(SObjectType objectType) { + String prefix = this.prefixBySObject.get(objectType); - public Id get(SObjectType objectType) { - return this.get(this.getPrefix(objectType)); - } + if (prefix == null) { + prefix = objectType.getDescribe().getKeyPrefix(); + this.prefixBySObject.put(objectType, prefix); + } - private Id get(String prefix) { - this.counter++; - return Id.valueOf(prefix + '0'.repeat(15 - prefix.length() - String.valueOf(this.counter).length()) + String.valueOf(this.counter)); - } - - private String getPrefix(SObjectType objectType) { - String prefix = this.prefixBySObject.get(objectType); - - if (prefix == null) { - prefix = objectType.getDescribe().getKeyPrefix(); - this.prefixBySObject.put(objectType, prefix); - } - - return prefix; - } - } + return prefix; + } + } } diff --git a/force-app/main/default/classes/DML_Test.cls b/force-app/main/default/classes/DML_Test.cls index b1f957a..0b0212d 100644 --- a/force-app/main/default/classes/DML_Test.cls +++ b/force-app/main/default/classes/DML_Test.cls @@ -2,7 +2,7 @@ * Copyright (c) 2026 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev) * Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/dml-lib/blob/main/LICENSE) * - * v3.0.0 + * v3.1.0 * * PMD False Positives: * - CyclomaticComplexity: It is a library and we tried to put everything into ONE class @@ -14,3822 +14,3799 @@ @SuppressWarnings('PMD.CyclomaticComplexity,PMD.CognitiveComplexity,PMD.ApexUnitTestClassShouldHaveRunAs,PMD.NcssMethodCount,PMD.NcssTypeCount') @IsTest private class DML_Test { - // ================================================ INSERT ================================================= - - @IsTest - static void toInsertWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toInsert(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 1 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ UPDATE ================================================= - - @IsTest - static void toUpdateWithEmptyRecordIds() { - // Setup - List recordIds = new List(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUpdate(DML.Records(recordIds)).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 result.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - @IsTest - static void toUpdateWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUpdate(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ UPSERT ================================================= - - @IsTest - static void toUpsertWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUpsert(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ DELETE ================================================= - - @IsTest - static void toDeleteWithoutExistingIds() { - // Setup - Account account = getAccount(1); - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toDelete(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only existing records can be registered as deleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toDeleteWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toDelete(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ HARD DELETE ================================================= - - @IsTest - static void toHardDeleteWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toHardDelete(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - @IsTest - static void toHardDeleteWithoutExistingIds() { - // Setup - Account account = getAccount(1); - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toHardDelete(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only existing records can be registered as deleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - // ================================================ UNDELETE ================================================= - - @IsTest - static void toUndeleteWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUndelete(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ PLATFORM EVENT ================================================= - - @IsTest - static void toPublishWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toPublish(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 1 result.'); - } - - // ================================================ DEBUG ================================================= - - @IsTest - static void dryRun() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account).dryRun(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted.'); - Assert.areEqual( - 4, - dmlStatementsAfter - dmlStatementsBefore, - 'DML statements should be 4, because one for savepoint, one for rollback, one for release savepoint, and one for the insert.' - ); - } - - @IsTest - static void dryRunWhenExceptionIsThrown() { - // Setup - Account account = getAccount(1); - account.Name = null; - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account).dryRun(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); - } - - @SuppressWarnings('PMD.ApexUnitTestClassShouldHaveAsserts') - @IsTest - static void preview() { - // Setup - Account account = getAccount(1); - - // Test - new DML().toInsert(account).preview(); - } - - // CONFIGURATION - - @IsTest - static void commitTransactionWhenExceptionIsThrown() { - // Setup - Account account = getAccount(1); - account.Name = null; - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account).toInsert(account).commitTransaction(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); - } - - @IsTest - static void commitTransactionWhenAllOrNoneIsFalse() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(account).allowPartialSuccess().commitTransaction(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); - Assert.areEqual('commitTransaction() is not supported when allOrNone=false', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void discardWork() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toInsert(account).discardWork().commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 0, because work was discarded.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted.'); - - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - } - - // ================================================ RECORDS ================================================= - - @IsTest - static void retrieveResultForWhenIncorrectIdentifierIsUsed() { - // Setup - Exception expectedException = null; - - // Test - Test.startTest(); - try { - DML.retrieveResultFor('dmlMockId'); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('No result found for dml identifier: dmlMockId', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void mockAllDmls() { - // Setup - Account account1 = getAccount(1); - - Contact contact1 = getContact(1); - contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); - - Opportunity opportunity1 = getOpportunity(1); - opportunity1.Id = DML.randomIdGenerator.get(Opportunity.SObjectType); - - DML.mock('dmlMockId').allDmls(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toInsert(account1).toUpdate(contact1).toDelete(opportunity1).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Opportunity], 'No records should be deleted from the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); - Assert.areEqual(Opportunity.SObjectType, result.deletesOf(Opportunity.SObjectType).objectType(), 'Deleted operation result should contain Opportunity object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Opportunity.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - } - - // ================================================ RESULTS ================================================= - - @IsTest - static void resultWhenNoOperations() { - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.all().size(), 'Result should contain 0 operation results.'); - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.merges().size(), 'Result should contain 0 merge operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - } - - // ================================================ DEPENDENCIES ================================================= - - @IsTest - static void selfDependency() { - // Setup - Account account1 = getAccount(1); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - new DML().toInsert(account1).toInsert(DML.Record(account1).withRelationship(Account.ParentId, account1)).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Self-dependency detected for record: ' + account1.Id), 'Expected exception message should be thrown.'); - } - - @IsTest - static void cyclicDependency() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - new DML() - .toInsert(account1) - .toInsert(DML.Record(account2).withRelationship(Account.ParentId, account1)) - .toInsert(DML.Record(account1).withRelationship(Account.ParentId, account2)) - .commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Cyclic dependencies detected among records.'), 'Expected exception message should be thrown.'); - } - - // ================================================================================================ - // =========================================== MOCKING ============================================ - // ================================================================================================ - - // ================================================ MOCKING - INSERT ================================================= - - @IsTest - static void toInsertSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].record(), 'Inserted operation record result should contain the record.'); - Assert.areEqual(account1.Id, result.insertsOf(Account.SObjectType).recordResults()[0].record().Id, 'Record result should return the mocked record.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - } - - @IsTest - static void toInsertSingleRecordWithMockingAndRelationship() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - - DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, accountResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult contactResult = result.insertsOf(Contact.SObjectType); - - Assert.areEqual(Contact.SObjectType, contactResult.objectType(), 'Inserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, contactResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, contactResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, contactResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(contactResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(contactResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); - } - - @IsTest - static void toInsertMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).toInsert(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(2, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted records.'); - Assert.areEqual(2, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[1].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - } - - @IsTest - static void toInsertMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).toInsert(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.insertsOf(Contact.SObjectType).objectType(), 'Inserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Contact.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(1, result.insertsOf(Contact.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.insertsOf(Contact.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isTrue(result.insertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.insertsOf(Contact.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); - } - - @IsTest - static void toInsertWithEmptyRecordWhenMocking() { - // Setup - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toInsert(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - @IsTest - static void toInsertWithMockingException() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnInserts(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toInsertWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnInserts(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toInsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Result should contain insert type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toInsertWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnInsertsFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toInsertWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnInsertsFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toInsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Result should contain insert type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - // ================================================ MOCKING - UPDATE ================================================= - - @IsTest - static void toUpdateSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account'; - - new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUpdateMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account 1'; - account2.Name = 'Updated Test Account 2'; - - new DML().toUpdate(account1).toUpdate(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(2, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated records.'); - Assert.areEqual(2, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[1].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); - } - - @IsTest - static void toUpdateMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); - - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account 1'; - contact1.FirstName = 'Updated Test Contact 1'; - - new DML().toUpdate(account1).toUpdate(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(2, result.updates().size(), 'Updated operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(1, result.updatesOf(Contact.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.updatesOf(Contact.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isTrue(result.updatesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.isNotNull(result.updatesOf(Contact.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Contact 1', contact1.FirstName, 'Contact 1 should be updated.'); - } - - @IsTest - static void toUpdateWithMockingException() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUpdates(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUpdateWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUpdates(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUpdate(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Result should contain update type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUpdateWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUpdatesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUpdateWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUpdatesFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUpdate(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Result should contain update type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUpdateWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toUpdate(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ MOCKING - UPSERT ================================================= - - @IsTest - static void toUpsertSingleRecordWhenIdIsNotSpecifiedWithMocking() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account'; - - new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - } - - @IsTest - static void toUpsertSingleRecordWhenIdIsSpecifiedWithMocking() { - // Setup - Account account1 = getAccount(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account'; - - new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - } - - @IsTest - static void toUpsertGenericSObjectListWithExternalIdFieldWithMocking() { - // Setup - Account account1 = getAccount(1); - List genericRecords = new List{ account1 }; - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toUpsert(genericRecords, Account.Id).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - } - - @IsTest - static void listSObjectOverloadsResolveRuntimeTypeAcrossOperationsWithMocking() { - // Setup - Account insertAccount = getAccount(101); - List insertRecords = new List{ insertAccount }; - DML.mock('genericInsertMockId').allInserts(); - - Account updateAccount = getAccount(102); - updateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - List updateRecords = new List{ updateAccount }; - DML.mock('genericUpdateMockId').allUpdates(); - - Account deleteAccount = getAccount(103); - deleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - List deleteRecords = new List{ deleteAccount }; - DML.mock('genericDeleteMockId').allDeletes(); - - Account hardDeleteAccount = getAccount(104); - hardDeleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - List hardDeleteRecords = new List{ hardDeleteAccount }; - DML.mock('genericHardDeleteMockId').allDeletes(); - - Account undeleteAccount = getAccount(105); - undeleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - List undeleteRecords = new List{ undeleteAccount }; - DML.mock('genericUndeleteMockId').allUndeletes(); - - // Test - Test.startTest(); - new DML().toInsert(insertRecords).identifier('genericInsertMockId').commitWork(); - new DML().toUpdate(updateRecords).identifier('genericUpdateMockId').commitWork(); - new DML().toDelete(deleteRecords).identifier('genericDeleteMockId').commitWork(); - new DML().toHardDelete(hardDeleteRecords).identifier('genericHardDeleteMockId').commitWork(); - new DML().toUndelete(undeleteRecords).identifier('genericUndeleteMockId').commitWork(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, DML.retrieveResultFor('genericInsertMockId').insertsOf(Account.SObjectType).recordResults().size(), 'Insert result should be keyed by Account SObjectType.'); - Assert.areEqual(1, DML.retrieveResultFor('genericUpdateMockId').updatesOf(Account.SObjectType).recordResults().size(), 'Update result should be keyed by Account SObjectType.'); - Assert.areEqual(1, DML.retrieveResultFor('genericDeleteMockId').deletesOf(Account.SObjectType).recordResults().size(), 'Delete result should be keyed by Account SObjectType.'); - Assert.areEqual(1, DML.retrieveResultFor('genericHardDeleteMockId').deletesOf(Account.SObjectType).recordResults().size(), 'Hard delete result should be keyed by Account SObjectType.'); - Assert.areEqual(1, DML.retrieveResultFor('genericUndeleteMockId').undeletesOf(Account.SObjectType).recordResults().size(), 'Undelete result should be keyed by Account SObjectType.'); - } - - @IsTest - static void listSObjectOverloadsThrowWhenMixedTypesProvided() { - // Setup - List mixedRecords = new List{ - getAccount(1), - getContact(1) - }; - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpsert(mixedRecords).identifier('mixedSObjectTypeMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown for mixed SObject types in one list operation.'); - Assert.isTrue(expectedException.getMessage().contains('Mixed SObject types'), 'Expected mixed SObject type validation message.'); - } - - @IsTest - static void toUpsertMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account 1'; - account2.Name = 'Upserted Test Account 2'; - - new DML().toUpsert(account1).toUpsert(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(2, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(2, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[1].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be upserted.'); - Assert.areEqual('Upserted Test Account 2', account2.Name, 'Account 2 should be upserted.'); - } - - @IsTest - static void toUpsertMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account 1'; - contact1.FirstName = 'Upserted Test Contact 1'; - - new DML().toUpsert(account1).toUpsert(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(2, result.upserts().size(), 'Upserted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.upsertsOf(Contact.SObjectType).objectType(), 'Upserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Contact.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isTrue(result.upsertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.upsertsOf(Contact.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Upserted Test Contact 1', contact1.FirstName, 'Contact 1 should be updated.'); - } - - @IsTest - static void toUpsertWithMockingException() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnUpserts(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUpsertWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnUpserts(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUpsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Result should contain upsert type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUpsertWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnUpsertsFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUpsertWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnUpsertsFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUpsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Result should contain upsert type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUpsertWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toUpsert(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ MOCKING - DELETE ================================================= - - @IsTest - static void toDeleteSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toDelete(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteSingleRecordByIdWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toDelete(account1.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toDelete(account1).toDelete(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteMultipleRecordsByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toDelete(new List{ account1.Id, account2.Id }).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toDelete(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - @IsTest - static void toDeleteMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toDelete(account1).toDelete(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should exist, because delete was mocked.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No Contact records should exist, because delete was mocked.'); - Assert.areEqual(2, result.deletes().size(), 'Deleted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.deletesOf(Contact.SObjectType).objectType(), 'Deleted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Contact.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(1, result.deletesOf(Contact.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Contact.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(result.deletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.deletesOf(Contact.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteWithMockingException() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletes(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toDelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toDeleteWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletes(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toDelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Result should contain delete type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toDeleteWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toDelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toDeleteWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toDelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Result should contain delete type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - // ================================================ MOCKING - HARD DELETE ================================================= - - @IsTest - static void toHardDeleteSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toHardDeleteMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toHardDelete(account1).toHardDelete(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toHardDeleteByIdWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toHardDelete(account1.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toHardDeleteByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toHardDelete(new Set{ account1.Id, account2.Id }).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toHardDeleteWithMockingException() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletes(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toHardDeleteWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toHardDeleteWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toHardDelete(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ MOCKING - UNDELETE ================================================= - - @IsTest - static void toUndeleteSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toUndelete(account1).toUndelete(account2).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(2, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteByIdWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toUndelete(account1.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toUndelete(new List{ account1.Id, account2.Id }).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toUndelete(account1).toUndelete(contact1).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should exist, because undelete was mocked.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No Contact records should exist, because undelete was mocked.'); - Assert.areEqual(2, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.undeletesOf(Contact.SObjectType).objectType(), 'Undeleted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Contact.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(result.undeletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.undeletesOf(Contact.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteWithMockingException() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUndeletes(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUndeleteWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUndeletes(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUndelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Result should contain undelete type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUndeleteWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUndeletesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUndeleteWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUndeletesFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUndelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Result should contain undelete type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUndeleteWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toUndelete(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ MOCKING - MERGE ================================================= - - @IsTest - static void toMergeSingleRecordWithMocking() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allMerges(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be in the database.'); - Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Account.SObjectType).operationType(), 'Merged operation result should contain merge type.'); - Assert.areEqual(1, result.mergesOf(Account.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, result.mergesOf(Account.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.isTrue(result.mergesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); - Assert.isNotNull(result.mergesOf(Account.SObjectType).recordResults()[0].id(), 'Merged operation result should contain a mocked record Id.'); - } - - @IsTest - static void toMergeMultipleRecordTypesWithMocking() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - Lead masterLead = getLead(1); - Lead duplicateLead = getLead(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - masterLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); - duplicateLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); - - DML.mock('dmlMockId').allMerges(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toMerge(masterAccount, duplicateAccount).toMerge(masterLead, duplicateLead).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should be in the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'No Lead records should be in the database.'); - Assert.areEqual(2, result.merges().size(), 'Merged operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(Lead.SObjectType, result.mergesOf(Lead.SObjectType).objectType(), 'Merged operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Account.SObjectType).operationType(), 'Merged operation result should contain merge type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Lead.SObjectType).operationType(), 'Merged operation result should contain merge type.'); - Assert.areEqual(1, result.mergesOf(Account.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, result.mergesOf(Account.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(1, result.mergesOf(Lead.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, result.mergesOf(Lead.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.isTrue(result.mergesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); - Assert.isTrue(result.mergesOf(Lead.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); - } - - @IsTest - static void toMergeWithMockingException() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnMerges(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toMergeWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnMerges(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toMerge(masterAccount, duplicateAccount).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Result should contain merge type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toMergeWithMockingExceptionForSpecificSObjectType() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnMergesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toMergeWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnMergesFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toMerge(masterAccount, duplicateAccount).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Result should contain merge type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toMergeWithEmptyRecordsWhenMocking() { - // Setup - Account masterAccount = getAccount(1); - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allMerges(); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toMerge(masterAccount, new List()).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify); - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - } - - // ================================================ MOCKING - PUBLISH ================================================= - - @IsTest - static void toPublishSingleRecordWithMocking() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toPublish(event).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - } - - @IsTest - static void toPublishSingleRecordWithMockingSpecificSObjectType() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').publishesFor(FlowOrchestrationEvent.SObjectType); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toPublish(event).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - } - - @IsTest - static void toPublishMultipleRecordsWithMocking() { - // Setup - FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); - FlowOrchestrationEvent event2 = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toPublish(event1).toPublish(event2).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published records.'); - Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].id(), 'Published operation result should contain a mocked record Id.'); - } - - @IsTest - static void toPublishWithMockingException() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').exceptionOnPublishes(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toPublish(event).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toPublishWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').exceptionOnPublishes(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toPublish(event).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Result should contain publish type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toPublishWithMockingExceptionForSpecificSObjectType() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').exceptionOnPublishesFor(FlowOrchestrationEvent.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toPublish(event).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toPublishWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').exceptionOnPublishesFor(FlowOrchestrationEvent.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toPublish(event).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); + @IsTest + static void toInsertWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toInsert(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 1 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toUpdateWithEmptyRecordIds() { + // Setup + List recordIds = new List(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUpdate(DML.Records(recordIds)).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 result.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toUpdateWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUpdate(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toUpsertWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUpsert(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toDeleteWithoutExistingIds() { + // Setup + Account account = getAccount(1); + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toDelete(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only existing records can be registered as deleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toDeleteWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toDelete(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toHardDeleteWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toHardDelete(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toHardDeleteWithoutExistingIds() { + // Setup + Account account = getAccount(1); + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toHardDelete(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only existing records can be registered as deleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + + @IsTest + static void toUndeleteWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUndelete(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toPublishWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toPublish(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 1 result.'); + } + + + @IsTest + static void dryRun() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account).dryRun(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted.'); + Assert.areEqual( + 4, + dmlStatementsAfter - dmlStatementsBefore, + 'DML statements should be 4, because one for savepoint, one for rollback, one for release savepoint, and one for the insert.' + ); + } + + @IsTest + static void dryRunWhenExceptionIsThrown() { + // Setup + Account account = getAccount(1); + account.Name = null; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account).dryRun(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); + } + + @SuppressWarnings('PMD.ApexUnitTestClassShouldHaveAsserts') + @IsTest + static void preview() { + // Setup + Account account = getAccount(1); + + // Test + new DML().toInsert(account).preview(); + } + + // CONFIGURATION + + @IsTest + static void commitTransactionWhenExceptionIsThrown() { + // Setup + Account account = getAccount(1); + account.Name = null; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account).toInsert(account).commitTransaction(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); + } + + @IsTest + static void commitTransactionWhenAllOrNoneIsFalse() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(account).allowPartialSuccess().commitTransaction(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); + Assert.areEqual('commitTransaction() is not supported when allOrNone=false', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void discardWork() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toInsert(account).discardWork().commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 0, because work was discarded.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted.'); + + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + } + + + @IsTest + static void retrieveResultForWhenIncorrectIdentifierIsUsed() { + // Setup + Exception expectedException = null; + + // Test + Test.startTest(); + try { + DML.retrieveResultFor('dmlMockId'); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('No result found for dml identifier: dmlMockId', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void mockAllDmls() { + // Setup + Account account1 = getAccount(1); + + Contact contact1 = getContact(1); + contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); + + Opportunity opportunity1 = getOpportunity(1); + opportunity1.Id = DML.randomIdGenerator.get(Opportunity.SObjectType); + + DML.mock('dmlMockId').allDmls(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toInsert(account1).toUpdate(contact1).toDelete(opportunity1).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Opportunity], 'No records should be deleted from the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); + Assert.areEqual(Opportunity.SObjectType, result.deletesOf(Opportunity.SObjectType).objectType(), 'Deleted operation result should contain Opportunity object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Opportunity.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + } + + + @IsTest + static void resultWhenNoOperations() { + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.all().size(), 'Result should contain 0 operation results.'); + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.merges().size(), 'Result should contain 0 merge operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + } + + + @IsTest + static void selfDependency() { + // Setup + Account account1 = getAccount(1); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + new DML().toInsert(account1).toInsert(DML.Record(account1).withRelationship(Account.ParentId, account1)).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Self-dependency detected for record: ' + account1.Id), 'Expected exception message should be thrown.'); + } + + @IsTest + static void cyclicDependency() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + new DML() + .toInsert(account1) + .toInsert(DML.Record(account2).withRelationship(Account.ParentId, account1)) + .toInsert(DML.Record(account1).withRelationship(Account.ParentId, account2)) + .commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Cyclic dependencies detected among records.'), 'Expected exception message should be thrown.'); + } + + + + @IsTest + static void toInsertSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].record(), 'Inserted operation record result should contain the record.'); + Assert.areEqual(account1.Id, result.insertsOf(Account.SObjectType).recordResults()[0].record().Id, 'Record result should return the mocked record.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + } + + @IsTest + static void toInsertSingleRecordWithMockingAndRelationship() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + + DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, accountResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult contactResult = result.insertsOf(Contact.SObjectType); + + Assert.areEqual(Contact.SObjectType, contactResult.objectType(), 'Inserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, contactResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, contactResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, contactResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(contactResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(contactResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); + } + + @IsTest + static void toInsertMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).toInsert(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(2, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted records.'); + Assert.areEqual(2, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[1].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + } + + @IsTest + static void toInsertMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).toInsert(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.insertsOf(Contact.SObjectType).objectType(), 'Inserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Contact.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(1, result.insertsOf(Contact.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.insertsOf(Contact.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isTrue(result.insertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.insertsOf(Contact.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); + } + + @IsTest + static void toInsertWithEmptyRecordWhenMocking() { + // Setup + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toInsert(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toInsertWithMockingException() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnInserts(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toInsertWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnInserts(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toInsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Result should contain insert type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toInsertWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnInsertsFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toInsertWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnInsertsFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toInsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Result should contain insert type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + + @IsTest + static void toUpdateSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account'; + + new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUpdateMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account 1'; + account2.Name = 'Updated Test Account 2'; + + new DML().toUpdate(account1).toUpdate(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(2, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated records.'); + Assert.areEqual(2, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[1].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); + } + + @IsTest + static void toUpdateMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); + + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account 1'; + contact1.FirstName = 'Updated Test Contact 1'; + + new DML().toUpdate(account1).toUpdate(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(2, result.updates().size(), 'Updated operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(1, result.updatesOf(Contact.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.updatesOf(Contact.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isTrue(result.updatesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.isNotNull(result.updatesOf(Contact.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Contact 1', contact1.FirstName, 'Contact 1 should be updated.'); + } + + @IsTest + static void toUpdateWithMockingException() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUpdates(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUpdateWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUpdates(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUpdate(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Result should contain update type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpdateWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUpdatesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUpdateWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUpdatesFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUpdate(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Result should contain update type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpdateWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toUpdate(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toUpsertSingleRecordWhenIdIsNotSpecifiedWithMocking() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account'; + + new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + } + + @IsTest + static void toUpsertSingleRecordWhenIdIsSpecifiedWithMocking() { + // Setup + Account account1 = getAccount(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account'; + + new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + } + + @IsTest + static void toUpsertGenericSObjectListWithExternalIdFieldWithMocking() { + // Setup + Account account1 = getAccount(1); + List genericRecords = new List{ account1 }; + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUpsert(genericRecords, Account.Id).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + } + + @IsTest + static void listSObjectOverloadsResolveRuntimeTypeAcrossOperationsWithMocking() { + // Setup + Account insertAccount = getAccount(101); + List insertRecords = new List{ insertAccount }; + DML.mock('genericInsertMockId').allInserts(); + + Account updateAccount = getAccount(102); + updateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List updateRecords = new List{ updateAccount }; + DML.mock('genericUpdateMockId').allUpdates(); + + Account deleteAccount = getAccount(103); + deleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List deleteRecords = new List{ deleteAccount }; + DML.mock('genericDeleteMockId').allDeletes(); + + Account hardDeleteAccount = getAccount(104); + hardDeleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List hardDeleteRecords = new List{ hardDeleteAccount }; + DML.mock('genericHardDeleteMockId').allDeletes(); + + Account undeleteAccount = getAccount(105); + undeleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List undeleteRecords = new List{ undeleteAccount }; + DML.mock('genericUndeleteMockId').allUndeletes(); + + // Test + Test.startTest(); + new DML().toInsert(insertRecords).identifier('genericInsertMockId').commitWork(); + new DML().toUpdate(updateRecords).identifier('genericUpdateMockId').commitWork(); + new DML().toDelete(deleteRecords).identifier('genericDeleteMockId').commitWork(); + new DML().toHardDelete(hardDeleteRecords).identifier('genericHardDeleteMockId').commitWork(); + new DML().toUndelete(undeleteRecords).identifier('genericUndeleteMockId').commitWork(); + Test.stopTest(); + + // Verify + Assert.areEqual( + 1, + DML.retrieveResultFor('genericInsertMockId').insertsOf(Account.SObjectType).recordResults().size(), + 'Insert result should be keyed by Account SObjectType.' + ); + Assert.areEqual( + 1, + DML.retrieveResultFor('genericUpdateMockId').updatesOf(Account.SObjectType).recordResults().size(), + 'Update result should be keyed by Account SObjectType.' + ); + Assert.areEqual( + 1, + DML.retrieveResultFor('genericDeleteMockId').deletesOf(Account.SObjectType).recordResults().size(), + 'Delete result should be keyed by Account SObjectType.' + ); + Assert.areEqual( + 1, + DML.retrieveResultFor('genericHardDeleteMockId').deletesOf(Account.SObjectType).recordResults().size(), + 'Hard delete result should be keyed by Account SObjectType.' + ); + Assert.areEqual( + 1, + DML.retrieveResultFor('genericUndeleteMockId').undeletesOf(Account.SObjectType).recordResults().size(), + 'Undelete result should be keyed by Account SObjectType.' + ); + } + + @IsTest + static void listSObjectOverloadsThrowWhenMixedTypesProvided() { + // Setup + List mixedRecords = new List{ getAccount(1), getContact(1) }; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpsert(mixedRecords).identifier('mixedSObjectTypeMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown for mixed SObject types in one list operation.'); + Assert.isTrue(expectedException.getMessage().contains('Mixed SObject types'), 'Expected mixed SObject type validation message.'); + } + + @IsTest + static void toUpsertMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account 1'; + account2.Name = 'Upserted Test Account 2'; + + new DML().toUpsert(account1).toUpsert(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(2, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(2, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[1].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be upserted.'); + Assert.areEqual('Upserted Test Account 2', account2.Name, 'Account 2 should be upserted.'); + } + + @IsTest + static void toUpsertMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account 1'; + contact1.FirstName = 'Upserted Test Contact 1'; + + new DML().toUpsert(account1).toUpsert(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(2, result.upserts().size(), 'Upserted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.upsertsOf(Contact.SObjectType).objectType(), 'Upserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Contact.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isTrue(result.upsertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.upsertsOf(Contact.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Upserted Test Contact 1', contact1.FirstName, 'Contact 1 should be updated.'); + } + + @IsTest + static void toUpsertWithMockingException() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnUpserts(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUpsertWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnUpserts(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUpsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Result should contain upsert type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpsertWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnUpsertsFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUpsertWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnUpsertsFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUpsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Result should contain upsert type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpsertWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toUpsert(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Result should contain publish type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toPublishWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toDeleteSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toDelete(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteSingleRecordByIdWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toDelete(account1.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - new DML().toPublish(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toDelete(account1).toDelete(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteMultipleRecordsByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toDelete(new List{ account1.Id, account2.Id }).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toDelete(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toDeleteMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toDelete(account1).toDelete(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should exist, because delete was mocked.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No Contact records should exist, because delete was mocked.'); + Assert.areEqual(2, result.deletes().size(), 'Deleted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.deletesOf(Contact.SObjectType).objectType(), 'Deleted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Contact.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(1, result.deletesOf(Contact.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Contact.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(result.deletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.deletesOf(Contact.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteWithMockingException() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletes(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toDelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toDeleteWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletes(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toDelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Result should contain delete type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toDeleteWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toDelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toDeleteWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toDelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Result should contain delete type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + + @IsTest + static void toHardDeleteSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toHardDeleteMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toHardDelete(account1).toHardDelete(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toHardDeleteByIdWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toHardDelete(account1.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toHardDeleteByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toHardDelete(new Set{ account1.Id, account2.Id }).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toHardDeleteWithMockingException() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletes(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toHardDeleteWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toHardDeleteWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toHardDelete(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toUndeleteSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUndelete(account1).toUndelete(account2).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(2, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteByIdWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toUndelete(account1.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toUndelete(new List{ account1.Id, account2.Id }).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUndelete(account1).toUndelete(contact1).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should exist, because undelete was mocked.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No Contact records should exist, because undelete was mocked.'); + Assert.areEqual(2, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.undeletesOf(Contact.SObjectType).objectType(), 'Undeleted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Contact.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(result.undeletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.undeletesOf(Contact.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteWithMockingException() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUndeletes(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUndeleteWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUndeletes(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUndelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Result should contain undelete type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUndeleteWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUndeletesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUndeleteWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUndeletesFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUndelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Result should contain undelete type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUndeleteWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toUndelete(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + + @IsTest + static void toMergeSingleRecordWithMocking() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allMerges(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be in the database.'); + Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Account.SObjectType).operationType(), 'Merged operation result should contain merge type.'); + Assert.areEqual(1, result.mergesOf(Account.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, result.mergesOf(Account.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.isTrue(result.mergesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); + Assert.isNotNull(result.mergesOf(Account.SObjectType).recordResults()[0].id(), 'Merged operation result should contain a mocked record Id.'); + } + + @IsTest + static void toMergeMultipleRecordTypesWithMocking() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + Lead masterLead = getLead(1); + Lead duplicateLead = getLead(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + masterLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); + duplicateLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); + + DML.mock('dmlMockId').allMerges(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toMerge(masterAccount, duplicateAccount).toMerge(masterLead, duplicateLead).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should be in the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'No Lead records should be in the database.'); + Assert.areEqual(2, result.merges().size(), 'Merged operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(Lead.SObjectType, result.mergesOf(Lead.SObjectType).objectType(), 'Merged operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Account.SObjectType).operationType(), 'Merged operation result should contain merge type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Lead.SObjectType).operationType(), 'Merged operation result should contain merge type.'); + Assert.areEqual(1, result.mergesOf(Account.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, result.mergesOf(Account.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(1, result.mergesOf(Lead.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, result.mergesOf(Lead.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.isTrue(result.mergesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); + Assert.isTrue(result.mergesOf(Lead.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); + } + + @IsTest + static void toMergeWithMockingException() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnMerges(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toMergeWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnMerges(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toMerge(masterAccount, duplicateAccount).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Result should contain merge type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toMergeWithMockingExceptionForSpecificSObjectType() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnMergesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toMergeWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnMergesFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toMerge(masterAccount, duplicateAccount).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Result should contain merge type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toMergeWithEmptyRecordsWhenMocking() { + // Setup + Account masterAccount = getAccount(1); + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allMerges(); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toMerge(masterAccount, new List()).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify); + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + } + + + @IsTest + static void toPublishSingleRecordWithMocking() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toPublish(event).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void toPublishSingleRecordWithMockingSpecificSObjectType() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').publishesFor(FlowOrchestrationEvent.SObjectType); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toPublish(event).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void toPublishMultipleRecordsWithMocking() { + // Setup + FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); + FlowOrchestrationEvent event2 = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toPublish(event1).toPublish(event2).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published records.'); + Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void toPublishWithMockingException() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').exceptionOnPublishes(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toPublish(event).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toPublishWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').exceptionOnPublishes(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toPublish(event).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Result should contain publish type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toPublishWithMockingExceptionForSpecificSObjectType() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').exceptionOnPublishesFor(FlowOrchestrationEvent.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toPublish(event).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toPublishWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').exceptionOnPublishesFor(FlowOrchestrationEvent.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toPublish(event).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - // Verify - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); - DML.Result result = DML.retrieveResultFor('dmlMockId'); + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Result should contain publish type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toPublishWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 1 result.'); - } + new DML().toPublish(new List()).identifier('dmlMockId').commitWork(); - // ================================================ MOCKING - INSERT IMMEDIATELY ================================================= + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void toInsertImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); + // Verify + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - DML.mock('dmlMockId').allInserts(); + DML.Result result = DML.retrieveResultFor('dmlMockId'); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 1 result.'); + } - new DML().identifier('dmlMockId').insertImmediately(account1); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + @IsTest + static void toInsertImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allInserts(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); + new DML().identifier('dmlMockId').insertImmediately(account1); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, accountResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void toInsertImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - DML.mock('dmlMockId').allInserts(); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); - new DML().identifier('dmlMockId').insertImmediately(new List{ account1, account2 }); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, accountResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + } - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + @IsTest + static void toInsertImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allInserts(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); + new DML().identifier('dmlMockId').insertImmediately(new List{ account1, account2 }); - Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Inserted operation result should contain the inserted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - // ================================================ MOCKING - UPSERT IMMEDIATELY ================================================= + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - @IsTest - static void toUpsertImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - DML.mock('dmlMockId').allUpserts(); + DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Inserted operation result should contain the inserted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + } - new DML().identifier('dmlMockId').upsertImmediately(account1); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + @IsTest + static void toUpsertImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allUpserts(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted to the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.upsertsOf(Account.SObjectType); + new DML().identifier('dmlMockId').upsertImmediately(account1); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, accountResult.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, accountResult.records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void toUpsertImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - DML.mock('dmlMockId').allUpserts(); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted to the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult accountResult = result.upsertsOf(Account.SObjectType); - new DML().identifier('dmlMockId').upsertImmediately(new List{ account1, account2 }); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, accountResult.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, accountResult.records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + } - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + @IsTest + static void toUpsertImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allUpserts(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.upsertsOf(Account.SObjectType); + new DML().identifier('dmlMockId').upsertImmediately(new List{ account1, account2 }); - Assert.areEqual(DML.OperationType.UPSERT_DML, accountResult.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Upserted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - // MOCKING - UPDATE IMMEDIATELY + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - @IsTest - static void toUpdateImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + DML.OperationResult accountResult = result.upsertsOf(Account.SObjectType); - DML.mock('dmlMockId').allUpdates(); + Assert.areEqual(DML.OperationType.UPSERT_DML, accountResult.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Upserted operation result should contain a mocked record Id.'); + } - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + // MOCKING - UPDATE IMMEDIATELY - new DML().identifier('dmlMockId').updateImmediately(account1); + @IsTest + static void toUpdateImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allUpdates(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated to the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.updatesOf(Account.SObjectType); + new DML().identifier('dmlMockId').updateImmediately(account1); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, accountResult.operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(1, accountResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void toUpdateImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated to the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - DML.mock('dmlMockId').allUpdates(); + DML.OperationResult accountResult = result.updatesOf(Account.SObjectType); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, accountResult.operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(1, accountResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + } - new DML().identifier('dmlMockId').updateImmediately(new List{ account1, account2 }); + @IsTest + static void toUpdateImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allUpdates(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated to the database.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.updatesOf(Account.SObjectType); + new DML().identifier('dmlMockId').updateImmediately(new List{ account1, account2 }); - Assert.areEqual(DML.OperationType.UPDATE_DML, accountResult.operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Updated operation result should contain the updated records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Updated operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - // ================================================ MOCKING - DELETE IMMEDIATELY ================================================= + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - @IsTest - static void toDeleteImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated to the database.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + DML.OperationResult accountResult = result.updatesOf(Account.SObjectType); - DML.mock('dmlMockId').allDeletes(); + Assert.areEqual(DML.OperationType.UPDATE_DML, accountResult.operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Updated operation result should contain the updated records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Updated operation result should contain a mocked record Id.'); + } - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().identifier('dmlMockId').deleteImmediately(account1); + @IsTest + static void toDeleteImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allDeletes(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + new DML().identifier('dmlMockId').deleteImmediately(account1); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void toDeleteImmediatelyByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - DML.mock('dmlMockId').allDeletes(); + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } - new DML().identifier('dmlMockId').deleteImmediately(account1.Id); + @IsTest + static void toDeleteImmediatelyByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allDeletes(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + new DML().identifier('dmlMockId').deleteImmediately(account1.Id); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void toDeleteImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - DML.mock('dmlMockId').allDeletes(); + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } - new DML().identifier('dmlMockId').deleteImmediately(new List{ account1, account2 }); + @IsTest + static void toDeleteImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allDeletes(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + new DML().identifier('dmlMockId').deleteImmediately(new List{ account1, account2 }); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void toDeleteImmediatelyMultipleRecordsByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - DML.mock('dmlMockId').allDeletes(); + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } - new DML().identifier('dmlMockId').deleteImmediately(new List{ account1.Id, account2.Id }); + @IsTest + static void toDeleteImmediatelyMultipleRecordsByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allDeletes(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + new DML().identifier('dmlMockId').deleteImmediately(new List{ account1.Id, account2.Id }); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - // ================================================ MOCKING - UNDELETE IMMEDIATELY ================================================= + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - @IsTest - static void toUndeleteImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - DML.mock('dmlMockId').allUndeletes(); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().identifier('dmlMockId').undeleteImmediately(account1); + @IsTest + static void toUndeleteImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allUndeletes(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + new DML().identifier('dmlMockId').undeleteImmediately(account1); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void toUndeleteImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - DML.mock('dmlMockId').allUndeletes(); + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } - new DML().identifier('dmlMockId').undeleteImmediately(new List{ account1, account2 }); + @IsTest + static void toUndeleteImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + DML.mock('dmlMockId').allUndeletes(); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); - Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + new DML().identifier('dmlMockId').undeleteImmediately(new List{ account1, account2 }); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); - @IsTest - static void undeleteImmediatelyByIdWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); - DML.mock('dmlMockId').allUndeletes(); + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); + Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - new DML().identifier('dmlMockId').undeleteImmediately(account1.Id); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); + } - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); + @IsTest + static void undeleteImmediatelyByIdWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + DML.mock('dmlMockId').allUndeletes(); - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } + new DML().identifier('dmlMockId').undeleteImmediately(account1.Id); - @IsTest - static void undeleteImmediatelyByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').undeleteImmediately(new List{ account1.Id, account2.Id }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - // ================================================ MOCKING - PUBLISH IMMEDIATELY ================================================= - - @IsTest - static void publishImmediatelySingleRecordWithMocking() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').publishImmediately(event); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - } - - @IsTest - static void publishImmediatelyMultipleRecordsWithMocking() { - // Setup - FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); - FlowOrchestrationEvent event2 = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').publishImmediately(new List{ event1, event2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published records.'); - Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].id(), 'Published operation result should contain a mocked record Id.'); - } - - // ================================================ MOCKING - OTHERS ================================================= - - @IsTest - static void retrieveResultForWhichDoesNotExist() { - // Setup - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - DML.retrieveResultFor('dmlMockId'); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('No result found for dml identifier: dmlMockId'), 'Expected exception message should be thrown.'); - } - - // ================================================ OPTIONS ================================================= - - @IsTest - static void sharedDmlInstanceWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Shared.identifier('dmlMockId').toInsert(account1).toInsert(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(2, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain 2 records.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Inserted operation result should contain a successful record result.'); - } - - @IsTest - static void sharedDmlInstanceDiscardWork() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Shared.toInsert(account1).toInsert(account2).discardWork(); - DML.Shared.commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be executed, because work was discarded.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No accounts should be inserted, because work was discarded.'); - Assert.isNull(account1.Id, 'Account 1 should not have an Id.'); - Assert.isNull(account2.Id, 'Account 2 should not have an Id.'); - } - - @IsTest - static void optionsAllOrNoneEnabled() { - // Setup - List accounts = new List{ - getAccount(1), - new Account() // Name is required - }; - - Exception expectedException = null; - - // Test - Test.startTest(); - Database.DmlOptions options = new Database.DmlOptions(); - options.optAllOrNone = true; - - try { - new DML().toInsert(accounts).options(options).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Required fields are missing: [Name]'), 'Expected exception message should be thrown.'); - } - - // ================================================ OPTIONS - ALLOW PARTIAL SUCCESS ================================================= - - // ================================================ OPTIONS - USER MODE ================================================= - - @IsTest - static void toInsertUserMode() { - // Setup - Case newCase = getCase(1); - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toInsert(newCase).commitWork(); // user mode by default - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithUserModeExplicitlySet() { - // Setup - Case newCase = getCase(1); - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toInsert(newCase).userMode().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - // ================================================================================================= - // ================================================ VALIDATION ================================================= - // ================================================================================================= - - // ================================================ VALIDATION - INSERT ================================================= - - @IsTest - static void toInsertWithInvalidRelationshipSingleRecord() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.LastName, newAccount)).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Invalid argument: LastName. Field supplied is not a relationship field.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithInvalidTargetRelationshipFieldSingleRecord() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.LastName, Account.Name, 'Test')).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Invalid argument: relationshipField. Field supplied is not a relationship field.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void recordWithEmptyId() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - new DML().toInsert(DML.Record(account.Id)).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Invalid argument: recordId. Record ID cannot be null.'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithInvalidExternalRelationshipSingleRecord() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.AccountId, Account.Name, 'Test')).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void toInsertWithInvalidTargetRelationshipFieldMultipleRecords() { - // Setup - Account newAccount = getAccount(1); - Contact newContact1 = getContact(1); - Contact newContact2 = getContact(2); - - List contacts = new List{ newContact1, newContact2 }; - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.LastName, newAccount)).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Invalid argument: LastName. Field supplied is not a relationship field.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithInvalidExternalRelationshipMultipleRecords() { - // Setup - Account newAccount = getAccount(1); - Contact newContact1 = getContact(1); - Contact newContact2 = getContact(2); - - List contacts = new List{ newContact1, newContact2 }; - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.LastName, Account.Name, 'Test')).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Invalid argument: relationshipField. Field supplied is not a relationship field.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void toInsertWithInvalidExternalIdFieldMultipleRecords() { - // Setup - Account newAccount = getAccount(1); - Contact newContact1 = getContact(1); - Contact newContact2 = getContact(2); - - List contacts = new List{ newContact1, newContact2 }; - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.AccountId, Account.Name, 'Test')).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - // ================================================ VALIDATION - UPDATE ================================================= - - @IsTest - static void toUpdateWithoutExistingIds() { - // Setup - Account account = getAccount(1); - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpdate(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only existing records can be updated.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - // ================================================ VALIDATION - UNDELETE ================================================= - - @IsTest - static void toUndeleteWithoutExistingIds() { - // Setup - Account account = getAccount(1); - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUndelete(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only deleted records can be undeleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - // ================================================ HOOK ================================================= - - public class MyHook implements DML.Hook { - private Boolean beforeCalled = false; - private Boolean afterCalled = false; - - public void before() { - beforeCalled = true; - } - - public void after(DML.Result result) { - afterCalled = true; - - Assert.isNotNull(result, 'Result should not be null.'); - } - } - - // HELPERS - - static Account getAccount(Integer index) { - return new Account(Name = 'Test Account ' + index); - } - - static Contact getContact(Integer index) { - return new Contact(FirstName = 'Test ' + index, LastName = 'Contact ' + index); - } - - static Opportunity getOpportunity(Integer index) { - return new Opportunity(Name = 'Test Opportunity ' + index, CloseDate = Date.today(), StageName = 'Prospecting'); - } - - static Lead getLead(Integer index) { - return new Lead(FirstName = 'Test ' + index, LastName = 'Lead ' + index, Company = 'Test Company ' + index); - } - - static Case getCase(Integer index) { - return new Case(Status = 'New', Origin = 'Web', Subject = 'Test ' + index); - } - - @SuppressWarnings('PMD.AvoidNonRestrictiveQueries') - static List getAccounts() { - return [SELECT Id, Name FROM Account]; - } - - static User minimumAccessUser() { - return new User( - Alias = 'newUser', - Email = 'newuser@testorg.com', - EmailEncodingKey = 'UTF-8', - LastName = 'Testing', - LanguageLocaleKey = 'en_US', - LocaleSidKey = 'en_US', - Profile = new Profile(Name = 'Minimum Access - Salesforce'), - TimeZoneSidKey = 'America/Los_Angeles', - UserName = 'btcdmllibuser@testorg.com' - ); - } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void undeleteImmediatelyByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').undeleteImmediately(new List{ account1.Id, account2.Id }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + + @IsTest + static void publishImmediatelySingleRecordWithMocking() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').publishImmediately(event); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void publishImmediatelyMultipleRecordsWithMocking() { + // Setup + FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); + FlowOrchestrationEvent event2 = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').publishImmediately(new List{ event1, event2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published records.'); + Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].id(), 'Published operation result should contain a mocked record Id.'); + } + + + @IsTest + static void retrieveResultForWhichDoesNotExist() { + // Setup + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + DML.retrieveResultFor('dmlMockId'); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('No result found for dml identifier: dmlMockId'), 'Expected exception message should be thrown.'); + } + + + @IsTest + static void sharedDmlInstanceWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Shared.identifier('dmlMockId').toInsert(account1).toInsert(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(2, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain 2 records.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Inserted operation result should contain a successful record result.'); + } + + @IsTest + static void sharedDmlInstanceDiscardWork() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Shared.toInsert(account1).toInsert(account2).discardWork(); + DML.Shared.commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be executed, because work was discarded.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No accounts should be inserted, because work was discarded.'); + Assert.isNull(account1.Id, 'Account 1 should not have an Id.'); + Assert.isNull(account2.Id, 'Account 2 should not have an Id.'); + } + + @IsTest + static void optionsAllOrNoneEnabled() { + // Setup + List accounts = new List{ + getAccount(1), + new Account() // Name is required + }; + + Exception expectedException = null; + + // Test + Test.startTest(); + Database.DmlOptions options = new Database.DmlOptions(); + options.optAllOrNone = true; + + try { + new DML().toInsert(accounts).options(options).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Required fields are missing: [Name]'), 'Expected exception message should be thrown.'); + } + + + + @IsTest + static void toInsertUserMode() { + // Setup + Case newCase = getCase(1); + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toInsert(newCase).commitWork(); // user mode by default + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithUserModeExplicitlySet() { + // Setup + Case newCase = getCase(1); + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toInsert(newCase).userMode().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + + + @IsTest + static void toInsertWithInvalidRelationshipSingleRecord() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.LastName, newAccount)).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Invalid argument: LastName. Field supplied is not a relationship field.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithInvalidTargetRelationshipFieldSingleRecord() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.LastName, Account.Name, 'Test')).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Invalid argument: relationshipField. Field supplied is not a relationship field.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void recordWithEmptyId() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + new DML().toInsert(DML.Record(account.Id)).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Invalid argument: recordId. Record ID cannot be null.'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithInvalidExternalRelationshipSingleRecord() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.AccountId, Account.Name, 'Test')).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void toInsertWithInvalidTargetRelationshipFieldMultipleRecords() { + // Setup + Account newAccount = getAccount(1); + Contact newContact1 = getContact(1); + Contact newContact2 = getContact(2); + + List contacts = new List{ newContact1, newContact2 }; + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.LastName, newAccount)).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Invalid argument: LastName. Field supplied is not a relationship field.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithInvalidExternalRelationshipMultipleRecords() { + // Setup + Account newAccount = getAccount(1); + Contact newContact1 = getContact(1); + Contact newContact2 = getContact(2); + + List contacts = new List{ newContact1, newContact2 }; + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.LastName, Account.Name, 'Test')).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Invalid argument: relationshipField. Field supplied is not a relationship field.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void toInsertWithInvalidExternalIdFieldMultipleRecords() { + // Setup + Account newAccount = getAccount(1); + Contact newContact1 = getContact(1); + Contact newContact2 = getContact(2); + + List contacts = new List{ newContact1, newContact2 }; + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.AccountId, Account.Name, 'Test')).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + + @IsTest + static void toUpdateWithoutExistingIds() { + // Setup + Account account = getAccount(1); + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpdate(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only existing records can be updated.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + + @IsTest + static void toUndeleteWithoutExistingIds() { + // Setup + Account account = getAccount(1); + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUndelete(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only deleted records can be undeleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + + public class MyHook implements DML.Hook { + private Boolean beforeCalled = false; + private Boolean afterCalled = false; + + public void before() { + beforeCalled = true; + } + + public void after(DML.Result result) { + afterCalled = true; + + Assert.isNotNull(result, 'Result should not be null.'); + } + } + + // HELPERS + + static Account getAccount(Integer index) { + return new Account(Name = 'Test Account ' + index); + } + + static Contact getContact(Integer index) { + return new Contact(FirstName = 'Test ' + index, LastName = 'Contact ' + index); + } + + static Opportunity getOpportunity(Integer index) { + return new Opportunity(Name = 'Test Opportunity ' + index, CloseDate = Date.today(), StageName = 'Prospecting'); + } + + static Lead getLead(Integer index) { + return new Lead(FirstName = 'Test ' + index, LastName = 'Lead ' + index, Company = 'Test Company ' + index); + } + + static Case getCase(Integer index) { + return new Case(Status = 'New', Origin = 'Web', Subject = 'Test ' + index); + } + + @SuppressWarnings('PMD.AvoidNonRestrictiveQueries') + static List getAccounts() { + return [SELECT Id, Name FROM Account]; + } + + static User minimumAccessUser() { + return new User( + Alias = 'newUser', + Email = 'newuser@testorg.com', + EmailEncodingKey = 'UTF-8', + LastName = 'Testing', + LanguageLocaleKey = 'en_US', + LocaleSidKey = 'en_US', + Profile = new Profile(Name = 'Minimum Access - Salesforce'), + TimeZoneSidKey = 'America/Los_Angeles', + UserName = 'btcdmllibuser@testorg.com' + ); + } } diff --git a/internal/main/default/classes/DML_Full_Test.cls b/internal/main/default/classes/DML_Full_Test.cls deleted file mode 100644 index 5bdd4d3..0000000 --- a/internal/main/default/classes/DML_Full_Test.cls +++ /dev/null @@ -1,7703 +0,0 @@ -/** - * Copyright (c) 2026 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev) - * Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/dml-lib/blob/main/LICENSE) - * - * v3.0.0 - * - * PMD False Positives: - * - CyclomaticComplexity: It is a library and we tried to put everything into ONE class - * - CognitiveComplexity: It is a library and we tried to put everything into ONE class - * - ApexUnitTestClassShouldHaveRunAs: System.runAs is used to test fls and sharing modes - * - NcssMethodCount: Some methods are longer because of amount of assertions - * - NcssTypeCount: It is a library and we tried to put everything into ONE class - **/ -@SuppressWarnings('PMD.CyclomaticComplexity,PMD.CognitiveComplexity,PMD.ApexUnitTestClassShouldHaveRunAs,PMD.NcssMethodCount,PMD.NcssTypeCount') -@IsTest -private class DML_Full_Test { - // ================================================ INSERT ================================================= - - @IsTest - static void toInsertSingleRecord() { - // Setup - Account account1 = getAccount(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toInsert(account1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be inserted.'); - - Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); - - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(operationResult.hasFailures(), 'Inserted operation result should not have failures.'); - } - - @IsTest - static void toInsertWithRelationshipSingleRecord() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.AccountId, newAccount)).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted.'); - - Assert.areEqual(newAccount.Id, newContact.AccountId, 'Contact should be related to Account.'); - } - - @IsTest - static void toInsertWithDifferentRecordTypesAndRelationships() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - Account account3 = getAccount(3); - Contact contact1 = getContact(1); - Opportunity opportunity1 = getOpportunity(1); - Lead lead1 = getLead(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDmlStatements(); - - DML.Result result = new DML() - .toInsert(account1) - .toInsert(account2) - .toInsert(DML.Record(account3).withRelationship(Account.ParentId, account2)) - .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account2)) - .toInsert(opportunity1) - .toInsert(lead1) - .commitWork(); - - Integer dmlsAfter = Limits.getDmlStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(dmlsAfter - dmlsBefore, 5, '5 DML statements should be executed'); - - Assert.areEqual(3, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Lead should be inserted.'); - - Assert.areEqual(account2.Id, contact1.AccountId, 'Contact should be related to Account 2.'); - Assert.areEqual(account2.Id, account3.ParentId, 'Account 3 should be related to Account 2.'); - - Assert.areEqual(4, result.inserts().size(), 'Inserted operation result should contain 4 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(3, operationResult.records().size(), 'Inserted operation result should contain the inserted records.'); - Assert.areEqual(3, operationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(operationResult.hasFailures(), 'Inserted operation result should not have failures.'); - - DML.OperationResult contactOperationResult = result.insertsOf(Contact.SObjectType); - - Assert.areEqual(1, contactOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, contactOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Contact.SObjectType, contactOperationResult.objectType(), 'Inserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, contactOperationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(contactOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); - - DML.OperationResult opportunityOperationResult = result.insertsOf(Opportunity.SObjectType); - - Assert.areEqual(1, opportunityOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Inserted operation result should contain Opportunity object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, opportunityOperationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(opportunityOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); - - DML.OperationResult leadOperationResult = result.insertsOf(Lead.SObjectType); - - Assert.areEqual(1, leadOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Inserted operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, leadOperationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(leadOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); - } - - @IsTest - static void toInsertMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toInsert(account1).toInsert(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); - - Assert.isNotNull(account1.Id, 'Account 1 should be inserted and have an Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); - - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result, because 2 Account records are grouped.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(2, operationResult.records().size(), 'Inserted operation result should contain the inserted records.'); - Assert.areEqual(2, operationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(operationResult.hasFailures(), 'Inserted operation result should not have failures.'); - } - - @IsTest - static void toInsertWithRelationshipMultipleRecords() { - // Setup - Account newAccount = getAccount(1); - Contact newContact1 = getContact(1); - Contact newContact2 = getContact(2); - - List contacts = new List{ newContact1, newContact2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.AccountId, newAccount)).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.areEqual(2, [SELECT COUNT() FROM Contact], 'Contacts should be inserted.'); - - Assert.areEqual(newAccount.Id, newContact1.AccountId, 'Contact should be related to Account.'); - Assert.areEqual(newAccount.Id, newContact2.AccountId, 'Contact 2 should be related to Account.'); - } - - @IsTest - static void toInsertMultipleRecordsTypes() { - // Setup - Account account1 = getAccount(1); - Opportunity opportunityToInsert = getOpportunity(1); - Lead leadToInsert = getLead(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toInsert(account1).toInsert(opportunityToInsert).toInsert(leadToInsert).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Lead should be inserted.'); - - Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); - Assert.isNotNull(opportunityToInsert.Id, 'Opportunity should be inserted and have an Id.'); - Assert.isNotNull(leadToInsert.Id, 'Lead should be inserted and have an Id.'); - - Assert.areEqual(3, result.inserts().size(), 'Inserted operation result should contain 3 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult accountOperationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(1, accountOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, accountOperationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(accountOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); - - DML.OperationResult opportunityOperationResult = result.insertsOf(Opportunity.SObjectType); - - Assert.areEqual(1, opportunityOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Inserted operation result should contain Opportunity object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, opportunityOperationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(opportunityOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); - - DML.OperationResult leadOperationResult = result.insertsOf(Lead.SObjectType); - - Assert.areEqual(1, leadOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Inserted operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, leadOperationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(leadOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); - } - - @IsTest - static void toInsertListOfRecords() { - // Setup - List accounts = new List{ getAccount(1), getAccount(2), getAccount(3) }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(accounts).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(3, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); - - Assert.isNotNull(accounts[0].Id, 'Account 1 should be inserted and have an Id.'); - Assert.isNotNull(accounts[1].Id, 'Account 2 should be inserted and have an Id.'); - Assert.isNotNull(accounts[2].Id, 'Account 3 should be inserted and have an Id.'); - } - - @IsTest - static void toInsertWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toInsert(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 1 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ INSERT IMMEDIATELY ================================================= - - @IsTest - static void insertImmediatelySingleRecord() { - // Setup - Account account1 = getAccount(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().insertImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be inserted.'); - Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); - Assert.areEqual(1, result.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(result.hasFailures(), 'Inserted operation result should not have failures.'); - } - - @IsTest - static void insertImmediatelyMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().insertImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); - - Assert.isNotNull(account1.Id, 'Account 1 should be inserted and have an Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); - - Assert.areEqual(2, result.records().size(), 'Inserted operation result should contain the inserted records.'); - Assert.areEqual(2, result.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(result.hasFailures(), 'Inserted operation result should not have failures.'); - } - - @IsTest - static void insertImmediatelyWithDmlRecord() { - // Setup - Account account1 = getAccount(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().insertImmediately(DML.Record(account1).with(Account.Name, 'DML Record Account')); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be inserted.'); - Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); - Assert.areEqual('DML Record Account', [SELECT Name FROM Account WHERE Id = :account1.Id].Name, 'Account name should be set.'); - Assert.areEqual(1, result.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(result.hasFailures(), 'Inserted operation result should not have failures.'); - } - - @IsTest - static void insertImmediatelyWithDmlRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().insertImmediately(DML.Records(new List{ account1, account2 }).with(Account.Name, 'DML Records Account')); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); - - Assert.isNotNull(account1.Id, 'Account 1 should be inserted and have an Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account WHERE Name = 'DML Records Account'], 'Account names should be set.'); - - Assert.areEqual(2, result.records().size(), 'Inserted operation result should contain the inserted records.'); - Assert.areEqual(2, result.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(result.hasFailures(), 'Inserted operation result should not have failures.'); - } - - // ================================================ UPDATE ================================================= - - @IsTest - static void toUpdateSingleRecord() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account'; - - DML.Result result = new DML().toUpdate(account1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); - - Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(operationResult.hasFailures(), 'Updated operation result should not have failures.'); - } - - @IsTest - static void toUpdateSingleRecordTwiceWithMergeOnDuplicate() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - account1.Name = 'Updated Test Account'; - account1.Website = 'https://www.updatedtestaccount.com'; - - DML db = new DML(); - - db.combineOnDuplicate(); - - db.toUpdate(account1); - - Account duplicatedAccount = new Account(Id = account1.Id, Name = 'Updated Test Account 2', Description = 'Updated Test Description'); - - db.toUpdate(duplicatedAccount); - - DML.Result result = db.commitWork(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); - - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(operationResult.hasFailures(), 'Updated operation result should not have failures.'); - Assert.areEqual('Updated Test Account 2', account1.Name, 'Account should be updated.'); - Assert.areEqual('https://www.updatedtestaccount.com', account1.Website, 'Account should be updated.'); - Assert.areEqual('Updated Test Description', account1.Description, 'Account should be updated.'); - } - - @IsTest - static void toUpdateWithEmptyRecordIds() { - // Setup - List recordIds = new List(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUpdate(DML.Records(recordIds)).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 result.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - @IsTest - static void toUpdateWithRelationshipSingleRecord() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - insert newContact; - - // Test - Test.startTest(); - new DML().toInsert(newAccount).toUpdate(DML.Record(newContact).withRelationship(Contact.AccountId, newAccount)).commitWork(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be updated.'); - - List contacts = [SELECT Id, AccountId FROM Contact WHERE Id = :newContact.Id]; - - Assert.areEqual(newAccount.Id, contacts[0].AccountId, 'Contact should be related to Account.'); - } - - @IsTest - static void toUpdateMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - insert new List{ account1, account2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account 1'; - account2.Name = 'Updated Test Account 2'; - - DML.Result result = new DML().toUpdate(account1).toUpdate(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be updated.'); - - Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(2, operationResult.records().size(), 'Updated operation result should contain the updated records.'); - Assert.areEqual(2, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(operationResult.hasFailures(), 'Updated operation result should not have failures.'); - } - - @IsTest - static void toUpdateWithRelationshipMultipleRecords() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - Contact newContact2 = getContact(2); - - List contactsToCreate = new List{ newContact, newContact2 }; - insert contactsToCreate; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(newAccount).toUpdate(DML.Records(contactsToCreate).withRelationship(Contact.AccountId, newAccount)).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed.'); - - Assert.areEqual(2, [SELECT COUNT() FROM Contact], 'Contacts should be updated.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - - List contacts = [SELECT Id, AccountId FROM Contact WHERE Id IN :contactsToCreate]; - - Assert.areEqual(newAccount.Id, contacts[0].AccountId, 'Contact should be related to Account.'); - Assert.areEqual(newAccount.Id, contacts[1].AccountId, 'Contact 2 should be related to Account.'); - } - - @IsTest - static void toUpdateMultipleRecordsTypes() { - // Setup - Account account1 = getAccount(1); - Opportunity opportunity1 = getOpportunity(1); - Lead lead1 = getLead(1); - - insert new List{ account1, opportunity1, lead1 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account'; - opportunity1.Name = 'Updated Test Opportunity'; - lead1.FirstName = 'Updated Test'; - - DML.Result result = new DML().toUpdate(account1).toUpdate(opportunity1).toUpdate(lead1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); - Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be updated.'); - Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Lead should be updated.'); - - Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); - Assert.areEqual('Updated Test Opportunity', opportunity1.Name, 'Opportunity should be updated.'); - Assert.areEqual('Updated Test', lead1.FirstName, 'Lead should be updated.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(3, result.updates().size(), 'Updated operation result should contain 3 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult accountOperationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, accountOperationResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, accountOperationResult.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(accountOperationResult.hasFailures(), 'Updated operation result should not have failures.'); - - DML.OperationResult opportunityOperationResult = result.updatesOf(Opportunity.SObjectType); - - Assert.areEqual(1, opportunityOperationResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Updated operation result should contain Opportunity object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, opportunityOperationResult.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(opportunityOperationResult.hasFailures(), 'Updated operation result should not have failures.'); - - DML.OperationResult leadOperationResult = result.updatesOf(Lead.SObjectType); - - Assert.areEqual(1, leadOperationResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Updated operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, leadOperationResult.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(leadOperationResult.hasFailures(), 'Updated operation result should not have failures.'); - } - - @IsTest - static void toUpdateListOfRecords() { - // Setup - List accounts = new List{ getAccount(1), getAccount(2), getAccount(3) }; - insert accounts; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - accounts[0].Name = 'Updated Test Account 1'; - accounts[1].Name = 'Updated Test Account 2'; - accounts[2].Name = 'Updated Test Account 3'; - - new DML().toUpdate(accounts).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(3, [SELECT COUNT() FROM Account], 'Accounts should be updated.'); - - Assert.areEqual('Updated Test Account 1', accounts[0].Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Account 2', accounts[1].Name, 'Account 2 should be updated.'); - Assert.areEqual('Updated Test Account 3', accounts[2].Name, 'Account 3 should be updated.'); - } - - @IsTest - static void toUpdateWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUpdate(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ UPDATE IMMEDIATELY ================================================= - - @IsTest - static void updateImmediatelySingleRecord() { - // Setup - Account account1 = getAccount(1); - insert account1; - account1.Name = 'Updated Test Account'; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().updateImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account WHERE Name = 'Updated Test Account'], 'Single record should be updated.'); - Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); - Assert.areEqual(1, result.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(result.hasFailures(), 'Updated operation result should not have failures.'); - } - - @IsTest - static void updateImmediatelyMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - insert new List{ account1, account2 }; - - account1.Name = 'Updated Test Account 1'; - account2.Name = 'Updated Test Account 2'; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().updateImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account WHERE Name IN ('Updated Test Account 1', 'Updated Test Account 2')], 'Accounts should be updated.'); - - Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); - - Assert.areEqual(2, result.records().size(), 'Updated operation result should contain the updated records.'); - Assert.areEqual(2, result.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(result.hasFailures(), 'Updated operation result should not have failures.'); - } - - @IsTest - static void updateImmediatelyWithDmlRecord() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().updateImmediately(DML.Record(account1).with(Account.Name, 'Updated DML Record Account')); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual('Updated DML Record Account', [SELECT Name FROM Account WHERE Id = :account1.Id].Name, 'Account name should be updated.'); - Assert.areEqual(1, result.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(result.hasFailures(), 'Updated operation result should not have failures.'); - } - - @IsTest - static void updateImmediatelyWithDmlRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - insert new List{ account1, account2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().updateImmediately(DML.Records(new List{ account1, account2 }).with(Account.Name, 'Updated DML Records Account')); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account WHERE Name = 'Updated DML Records Account'], 'Account names should be updated.'); - - Assert.areEqual(2, result.records().size(), 'Updated operation result should contain the updated records.'); - Assert.areEqual(2, result.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(result.hasFailures(), 'Updated operation result should not have failures.'); - } - - // ================================================ UPSERT ================================================= - - @IsTest - static void toUpsertSingleExistingRecord() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account'; - - DML.Result result = new DML().toUpsert(account1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); - - Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.isFalse(operationResult.hasFailures(), 'Upserted operation result should not have failures.'); - } - - @IsTest - static void toUpsertSingleNewRecord() { - // Setup - Account account1 = getAccount(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUpsert(account1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - - Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.isFalse(operationResult.hasFailures(), 'Upserted operation result should not have failures.'); - } - - @IsTest - static void toUpsertMultipleExistingRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - insert new List{ account1, account2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account 1'; - account2.Name = 'Updated Test Account 2'; - - new DML().toUpsert(account1).toUpsert(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be updated.'); - - Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); - } - - @IsTest - static void toUpsertMultipleNewRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toUpsert(account1).toUpsert(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); - - Assert.isNotNull(account1.Id, 'Account 1 should be inserted and have an Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); - } - - @IsTest - static void toUpsertExistingAndNewRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account 1'; - - new DML().toUpsert(account1).toUpsert(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Both accounts should be upserted.'); - - Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); - } - - @IsTest - static void toUpsertListOfRecords() { - // Setup - List existingAccounts = new List{ getAccount(1), getAccount(2), getAccount(3) }; - insert existingAccounts; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - existingAccounts[0].Name = 'Updated Test Account 1'; - existingAccounts[1].Name = 'Updated Test Account 2'; - existingAccounts[2].Name = 'Updated Test Account 3'; - - List newAccounts = new List{ getAccount(1), getAccount(2), getAccount(3) }; - - new DML().toUpsert(existingAccounts).toUpsert(newAccounts).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(6, [SELECT COUNT() FROM Account], 'Accounts should be updated and inserted.'); - - Assert.areEqual('Updated Test Account 1', existingAccounts[0].Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Account 2', existingAccounts[1].Name, 'Account 2 should be updated.'); - Assert.areEqual('Updated Test Account 3', existingAccounts[2].Name, 'Account 3 should be updated.'); - - Assert.isNotNull(newAccounts[0].Id, 'New Account 1 should be inserted and have an Id.'); - Assert.isNotNull(newAccounts[1].Id, 'New Account 2 should be inserted and have an Id.'); - Assert.isNotNull(newAccounts[2].Id, 'New Account 3 should be inserted and have an Id.'); - } - - @IsTest - static void toUpsertWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUpsert(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ UPSERT IMMEDIATELY ================================================= - - @IsTest - static void upsertImmediatelySingleRecord() { - // Setup - Account account1 = getAccount(1); - insert account1; - account1.Name = 'Upserted Test Account'; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().upsertImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account WHERE Name = 'Upserted Test Account'], 'Single record should be upserted.'); - Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); - Assert.areEqual(1, result.records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, result.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.isFalse(result.hasFailures(), 'Upserted operation result should not have failures.'); - } - - @IsTest - static void upsertImmediatelyMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - insert account1; // Make account1 existing for upsert - - account1.Name = 'Upserted Test Account 1'; - // account2 is new for upsert - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().upsertImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be upserted.'); - - Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); - - Assert.areEqual(2, result.records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(2, result.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.isFalse(result.hasFailures(), 'Upserted operation result should not have failures.'); - } - - @IsTest - static void upsertImmediatelyWithDmlRecord() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().upsertImmediately(DML.Record(account1).with(Account.Name, 'Upserted DML Record Account')); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual('Upserted DML Record Account', [SELECT Name FROM Account WHERE Id = :account1.Id].Name, 'Account name should be upserted.'); - Assert.areEqual(1, result.records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, result.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.isFalse(result.hasFailures(), 'Upserted operation result should not have failures.'); - } - - @IsTest - static void upsertImmediatelyWithDmlRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - insert account1; // Make account1 existing for upsert - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().upsertImmediately(DML.Records(new List{ account1, account2 }).with(Account.Name, 'Upserted DML Records Account')); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be upserted.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account WHERE Name = 'Upserted DML Records Account'], 'Account names should be set.'); - - Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); - - Assert.areEqual(2, result.records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(2, result.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.isFalse(result.hasFailures(), 'Upserted operation result should not have failures.'); - } - - // ================================================ DELETE ================================================= - - @IsTest - static void toDeleteSingleRecordById() { - // Setup - Account account = insertAccount(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toDelete(account.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); - } - - @IsTest - static void toDeleteSingleRecord() { - // Setup - Account account1 = insertAccount(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toDelete(account1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(operationResult.hasFailures(), 'Deleted operation result should not have failures.'); - } - - @IsTest - static void toDeleteMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - insert new List{ account1, account2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toDelete(account1).toDelete(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(2, operationResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, operationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(operationResult.hasFailures(), 'Deleted operation result should not have failures.'); - } - - @IsTest - static void toDeleteListOfRecords() { - // Setup - List accounts = insertAccounts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toDelete(accounts).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - } - - @IsTest - static void toDeleteMultipleRecordsById() { - // Setup - List accounts = insertAccounts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - Set accountIds = new Map(accounts).keySet(); - - new DML().toDelete(accountIds).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - } - - @IsTest - static void toDeleteMultipleRecordsTypes() { - // Setup - Account account1 = getAccount(1); - Opportunity opportunity1 = getOpportunity(1); - Lead lead1 = getLead(1); - - insert new List{ account1, opportunity1, lead1 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toDelete(account1).toDelete(opportunity1).toDelete(lead1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); - Assert.areEqual(0, [SELECT COUNT() FROM Opportunity], 'Opportunity should be deleted.'); - Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'Lead should be deleted.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(3, result.deletes().size(), 'Deleted operation result should contain 3 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult accountOperationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, accountOperationResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountOperationResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(accountOperationResult.hasFailures(), 'Deleted operation result should not have failures.'); - - DML.OperationResult opportunityOperationResult = result.deletesOf(Opportunity.SObjectType); - - Assert.areEqual(1, opportunityOperationResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Deleted operation result should contain Opportunity object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, opportunityOperationResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(opportunityOperationResult.hasFailures(), 'Deleted operation result should not have failures.'); - - DML.OperationResult leadOperationResult = result.deletesOf(Lead.SObjectType); - - Assert.areEqual(1, leadOperationResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Deleted operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, leadOperationResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(leadOperationResult.hasFailures(), 'Deleted operation result should not have failures.'); - } - - @IsTest - static void toDeleteWithoutExistingIds() { - // Setup - Account account = getAccount(1); - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toDelete(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only existing records can be registered as deleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toDeleteWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toDelete(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ DELETE IMMEDIATELY ================================================= - - @IsTest - static void deleteImmediatelySingleRecord() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().deleteImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Single record should be deleted.'); - Assert.areEqual(1, result.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(result.hasFailures(), 'Deleted operation result should not have failures.'); - } - - @IsTest - static void deleteImmediatelySingleRecordById() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().deleteImmediately(account1.Id); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Single record should be deleted.'); - Assert.areEqual(1, result.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(result.hasFailures(), 'Deleted operation result should not have failures.'); - } - - @IsTest - static void deleteImmediatelyMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - insert new List{ account1, account2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().deleteImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - - Assert.areEqual(2, result.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, result.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(result.hasFailures(), 'Deleted operation result should not have failures.'); - } - - @IsTest - static void deleteImmediatelyMultipleRecordsByIds() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - insert new List{ account1, account2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.OperationResult result = new DML().deleteImmediately(new List{ account1.Id, account2.Id }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - - Assert.areEqual(2, result.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, result.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(result.hasFailures(), 'Deleted operation result should not have failures.'); - } - - // ================================================ HARD DELETE ================================================= - - @IsTest - static void toHardDeleteSingleRecordById() { - // Setup - Account account = insertAccount(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toHardDelete(account.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be hard deleted.'); - // No assertion with ALL ROWS, because there is Salesforce error - } - - @IsTest - static void toHardDeleteSingleRecord() { - // Setup - Account account1 = insertAccount(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toHardDelete(account1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be hard deleted.'); - // No assertion with ALL ROWS, because there is Salesforce error - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Hard deleted operation result should contain the deleted record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Hard deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Hard deleted operation result should contain delete type.'); - Assert.isFalse(operationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); - } - - @IsTest - static void toHardDeleteMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - insert new List{ account1, account2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toHardDelete(account1).toHardDelete(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be hard deleted.'); - // No assertion with ALL ROWS, because there is Salesforce error - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(2, operationResult.records().size(), 'Hard deleted operation result should contain the deleted records.'); - Assert.areEqual(2, operationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Hard deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Hard deleted operation result should contain delete type.'); - Assert.isFalse(operationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); - } - - @IsTest - static void toHardDeleteListOfRecords() { - // Setup - List accounts = insertAccounts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toHardDelete(accounts).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be hard deleted.'); - // No assertion with ALL ROWS, because there is Salesforce error - } - - @IsTest - static void toHardDeleteMultipleRecordsById() { - // Setup - List accounts = insertAccounts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - Set accountIds = new Map(accounts).keySet(); - - new DML().toHardDelete(accountIds).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be hard deleted.'); - // No assertion with ALL ROWS, because there is Salesforce error - } - - @IsTest - static void toHardDeleteMultipleRecordsTypes() { - // Setup - Account account1 = getAccount(1); - Opportunity opportunity1 = getOpportunity(1); - Lead lead1 = getLead(1); - - insert new List{ account1, opportunity1, lead1 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toHardDelete(account1).toHardDelete(opportunity1).toHardDelete(lead1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(6, dmlsAfter - dmlsBefore, '6 DML statements should be executed (3 deletes + 3 hard deletes).'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be hard deleted.'); - Assert.areEqual(0, [SELECT COUNT() FROM Opportunity], 'Opportunity should be hard deleted.'); - Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'Lead should be hard deleted.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(3, result.deletes().size(), 'Deleted operation result should contain 3 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult accountOperationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, accountOperationResult.records().size(), 'Hard deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Hard deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountOperationResult.operationType(), 'Hard deleted operation result should contain delete type.'); - Assert.isFalse(accountOperationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); - - DML.OperationResult opportunityOperationResult = result.deletesOf(Opportunity.SObjectType); - - Assert.areEqual(1, opportunityOperationResult.records().size(), 'Hard deleted operation result should contain the deleted record.'); - Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); - Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Hard deleted operation result should contain Opportunity object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, opportunityOperationResult.operationType(), 'Hard deleted operation result should contain delete type.'); - Assert.isFalse(opportunityOperationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); - - DML.OperationResult leadOperationResult = result.deletesOf(Lead.SObjectType); - - Assert.areEqual(1, leadOperationResult.records().size(), 'Hard deleted operation result should contain the deleted record.'); - Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); - Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Hard deleted operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, leadOperationResult.operationType(), 'Hard deleted operation result should contain delete type.'); - Assert.isFalse(leadOperationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); - } - - @IsTest - static void toHardDeleteWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toHardDelete(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - @IsTest - static void toHardDeleteWithoutExistingIds() { - // Setup - Account account = getAccount(1); - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toHardDelete(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only existing records can be registered as deleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - // ================================================ UNDELETE ================================================= - - @IsTest - static void toUndeleteSingleRecordById() { - // Setup - Account account = insertAccount(); - delete account; - - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toUndelete(account.Id).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be undeleted.'); - } - - @IsTest - static void toUndeleteSingleRecord() { - // Setup - Account account1 = insertAccount(); - delete account1; - - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toUndelete(account1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be undeleted.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(operationResult.hasFailures(), 'Undeleted operation result should not have failures.'); - } - - @IsTest - static void toUndeleteMultipleRecordsById() { - // Setup - List accounts = insertAccounts(); - delete accounts; - - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - Set accountIds = new Map(accounts).keySet(); - - new DML().toUndelete(accountIds).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(accounts.size(), [SELECT COUNT() FROM Account], 'Accounts should be undeleted.'); - } - - @IsTest - static void toUndeleteMultipleRecords() { - // Setup - List accounts = insertAccounts(); - delete accounts; - - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toUndelete(accounts).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(accounts.size(), [SELECT COUNT() FROM Account], 'Accounts should be undeleted.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(3, operationResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(3, operationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(operationResult.hasFailures(), 'Undeleted operation result should not have failures.'); - } - - @IsTest - static void toUndeleteMultipleRecordsTypes() { - // Setup - Account account1 = getAccount(1); - Opportunity opportunity1 = getOpportunity(1); - Lead lead1 = getLead(1); - - insert new List{ account1, opportunity1, lead1 }; - delete new List{ account1, opportunity1, lead1 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toUndelete(account1).toUndelete(opportunity1).toUndelete(lead1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be undeleted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be undeleted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Lead should be undeleted.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(3, result.undeletes().size(), 'Undeleted operation result should contain 3 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult accountOperationResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(1, accountOperationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountOperationResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(accountOperationResult.hasFailures(), 'Undeleted operation result should not have failures.'); - - DML.OperationResult opportunityOperationResult = result.undeletesOf(Opportunity.SObjectType); - - Assert.areEqual(1, opportunityOperationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Undeleted operation result should contain Opportunity object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, opportunityOperationResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(opportunityOperationResult.hasFailures(), 'Undeleted operation result should not have failures.'); - - DML.OperationResult leadOperationResult = result.undeletesOf(Lead.SObjectType); - - Assert.areEqual(1, leadOperationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Undeleted operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, leadOperationResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(leadOperationResult.hasFailures(), 'Undeleted operation result should not have failures.'); - } - - @IsTest - static void toUndeleteWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toUndelete(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ UNDELETE IMMEDIATELY ================================================= - - @IsTest - static void undeleteImmediatelySingleRecord() { - // Setup - Account account1 = getAccount(1); - insert account1; - delete account1; - - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult result = new DML().undeleteImmediately(account1); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be undeleted.'); - Assert.areEqual(1, result.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(result.hasFailures(), 'Undeleted operation result should not have failures.'); - } - - @IsTest - static void undeleteImmediatelySingleRecordById() { - // Setup - Account account1 = getAccount(1); - insert account1; - delete account1; - - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult result = new DML().undeleteImmediately(account1.Id); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be undeleted.'); - Assert.areEqual(1, result.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(result.hasFailures(), 'Undeleted operation result should not have failures.'); - } - - @IsTest - static void undeleteImmediatelyMultipleRecords() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - insert new List{ account1, account2 }; - delete new List{ account1, account2 }; - - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult result = new DML().undeleteImmediately(new List{ account1, account2 }); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be undeleted.'); - - Assert.areEqual(2, result.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, result.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(result.hasFailures(), 'Undeleted operation result should not have failures.'); - } - - @IsTest - static void undeleteImmediatelyMultipleRecordsByIds() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - insert new List{ account1, account2 }; - delete new List{ account1, account2 }; - - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult result = new DML().undeleteImmediately(new List{ account1.Id, account2.Id }); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be undeleted.'); - - Assert.areEqual(2, result.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, result.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Account.SObjectType, result.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(result.hasFailures(), 'Undeleted operation result should not have failures.'); - } - - // ================================================ MERGE ================================================= - - @IsTest - static void toMergeSingleDuplicateRecord() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - insert new List{ masterAccount, duplicateAccount }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toMerge(masterAccount, duplicateAccount).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); - Assert.areEqual(masterAccount.Id, [SELECT Id FROM Account LIMIT 1].Id, 'Master account should survive the merge.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); - Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); - } - - @IsTest - static void toMergeSingleDuplicateRecordById() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - insert new List{ masterAccount, duplicateAccount }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toMerge(masterAccount, duplicateAccount.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); - Assert.areEqual(masterAccount.Id, [SELECT Id FROM Account LIMIT 1].Id, 'Master account should survive the merge.'); - - Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); - Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); - } - - @IsTest - static void toMergeMultipleDuplicateRecords() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount1 = getAccount(2); - Account duplicateAccount2 = getAccount(3); - insert new List{ masterAccount, duplicateAccount1, duplicateAccount2 }; - - List duplicateAccounts = new List{ duplicateAccount1, duplicateAccount2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toMerge(masterAccount, duplicateAccounts).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); - Assert.areEqual(masterAccount.Id, [SELECT Id FROM Account LIMIT 1].Id, 'Master account should survive the merge.'); - - Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(2, operationResult.records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); - Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); - } - - @IsTest - static void toMergeMultipleDuplicateRecordsById() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount1 = getAccount(2); - Account duplicateAccount2 = getAccount(3); - insert new List{ masterAccount, duplicateAccount1, duplicateAccount2 }; - - Set duplicateAccountIds = new Set{ duplicateAccount1.Id, duplicateAccount2.Id }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toMerge(masterAccount, duplicateAccountIds).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); - Assert.areEqual(masterAccount.Id, [SELECT Id FROM Account LIMIT 1].Id, 'Master account should survive the merge.'); - - Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(2, operationResult.records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); - Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); - } - - @IsTest - static void toMergeWithRelatedRecords() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - insert new List{ masterAccount, duplicateAccount }; - - Contact contactOnDuplicate = getContact(1); - contactOnDuplicate.AccountId = duplicateAccount.Id; - insert contactOnDuplicate; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toMerge(masterAccount, duplicateAccount).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should still exist.'); - - Contact reparentedContact = [SELECT Id, AccountId FROM Contact WHERE Id = :contactOnDuplicate.Id]; - Assert.areEqual(masterAccount.Id, reparentedContact.AccountId, 'Contact should be reparented to master account.'); - } - - @IsTest - static void toMergeWithoutExistingMasterRecord() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - insert duplicateAccount; - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toMerge(masterAccount, duplicateAccount).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only existing records can be merged.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toMergeWithoutExistingDuplicateRecord() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - insert masterAccount; - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toMerge(masterAccount, duplicateAccount).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toMergeLeads() { - // Setup - Lead masterLead = getLead(1); - Lead duplicateLead = getLead(2); - insert new List{ masterLead, duplicateLead }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toMerge(masterLead, duplicateLead).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Only master lead should remain after merge.'); - Assert.areEqual(masterLead.Id, [SELECT Id FROM Lead LIMIT 1].Id, 'Master lead should survive the merge.'); - - Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); - - DML.OperationResult operationResult = result.mergesOf(Lead.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(Lead.SObjectType, operationResult.objectType(), 'Merged operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); - Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); - } - - @IsTest - static void toMergeContacts() { - // Setup - Contact masterContact = getContact(1); - Contact duplicateContact = getContact(2); - insert new List{ masterContact, duplicateContact }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toMerge(masterContact, duplicateContact).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Only master contact should remain after merge.'); - Assert.areEqual(masterContact.Id, [SELECT Id FROM Contact LIMIT 1].Id, 'Master contact should survive the merge.'); - - Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); - - DML.OperationResult operationResult = result.mergesOf(Contact.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(Contact.SObjectType, operationResult.objectType(), 'Merged operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); - Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); - } - - // ================================================ PLATFORM EVENT ================================================= - - @IsTest - static void toPublishSingleRecord() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toPublish(event).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - - DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Published operation result should contain publish type.'); - Assert.isTrue(operationResult.hasFailures(), 'Published operation result should have failures.'); - } - - @IsTest - static void toPublishMultipleRecords() { - // Setup - List events = new List{ new FlowOrchestrationEvent(), new FlowOrchestrationEvent() }; - - Exception expectedException = null; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = null; - try { - result = new DML().toPublish(events).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.isNull(expectedException, 'Expected exception should not be thrown.'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - - DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); - - Assert.areEqual(2, operationResult.records().size(), 'Published operation result should contain the published records.'); - Assert.areEqual(2, operationResult.recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Published operation result should contain publish type.'); - Assert.isTrue(operationResult.hasFailures(), 'Published operation result should have failures.'); - } - - @IsTest - static void toPublishWithEmptyRecords() { - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toPublish(new List()).commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 1 result.'); - } - - // ================================================ PLATFORM EVENT - IMMEDIATELY ================================================= - - @IsTest - static void publishImmediatelySingleRecord() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult result = new DML().publishImmediately(event); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, result.records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, result.recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, result.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, result.operationType(), 'Published operation result should contain publish type.'); - } - - @IsTest - static void publishImmediatelyMultipleRecords() { - // Setup - List events = new List{ new FlowOrchestrationEvent(), new FlowOrchestrationEvent() }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.OperationResult operationResult = new DML().publishImmediately(events); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, operationResult.records().size(), 'Published operation result should contain the published records.'); - Assert.areEqual(2, operationResult.recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Published operation result should contain publish type.'); - } - - // ================================================ DEBUG ================================================= - - @IsTest - static void dryRun() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account).dryRun(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted.'); - Assert.areEqual( - 4, - dmlStatementsAfter - dmlStatementsBefore, - 'DML statements should be 4, because one for savepoint, one for rollback, one for release savepoint, and one for the insert.' - ); - } - - @IsTest - static void dryRunWhenExceptionIsThrown() { - // Setup - Account account = getAccount(1); - account.Name = null; - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account).dryRun(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); - } - - @SuppressWarnings('PMD.ApexUnitTestClassShouldHaveAsserts') - @IsTest - static void preview() { - // Setup - Account account = getAccount(1); - - // Test - new DML().toInsert(account).preview(); - } - - // CONFIGURATION - - @IsTest - static void commitTransaction() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account).commitTransaction(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.isTrue(dmlStatementsAfter - dmlStatementsBefore >= 3, 'DML statements should be greater than or equal to 3, because savepoint was set and rollback was called.'); - } - - @IsTest - static void commitTransactionWhenExceptionIsThrown() { - // Setup - Account account = getAccount(1); - account.Name = null; - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account).toInsert(account).commitTransaction(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); - } - - @IsTest - static void commitTransactionWhenAllOrNoneIsFalse() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(account).allowPartialSuccess().commitTransaction(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); - Assert.areEqual('commitTransaction() is not supported when allOrNone=false', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void discardWork() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toInsert(account).discardWork().commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 0, because work was discarded.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted.'); - - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - } - - // ================================================ RECORD ================================================= - - @IsTest - static void singleSObjectRecordWithValue() { - // Setup - Account newAccount = getAccount(1); - - // Test - Test.startTest(); - new DML() - .toInsert(DML.Record(newAccount).with(Account.Name, 'New Test Account').with(Account.Industry, 'New Test Industry').with(Account.Description, 'New Test Description')) - .commitWork(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - - List accounts = [SELECT Id, Name, Industry, Description FROM Account LIMIT 1]; - - Assert.areEqual('New Test Account', accounts[0].Name, 'Account name should be "New Test Account".'); - Assert.areEqual('New Test Industry', accounts[0].Industry, 'Account industry should be "New Test Industry".'); - Assert.areEqual('New Test Description', accounts[0].Description, 'Account description should be "New Test Description".'); - } - - @IsTest - static void singleIdRecordWithValue() { - // Setup - Account newAccount = getAccount(1); - insert newAccount; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML() - .toUpdate( - DML.Record(newAccount.Id).with(Account.Name, 'New Test Account').with(Account.Industry, 'New Test Industry').with(Account.Description, 'New Test Description') - ) - .commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); - - List accounts = [SELECT Id, Name, Industry, Description FROM Account LIMIT 1]; - - Assert.areEqual('New Test Account', accounts[0].Name, 'Account name should be "New Test Account".'); - Assert.areEqual('New Test Industry', accounts[0].Industry, 'Account industry should be "New Test Industry".'); - Assert.areEqual('New Test Description', accounts[0].Description, 'Account description should be "New Test Description".'); - } - - // ================================================ RECORDS ================================================= - - @IsTest - static void multipleSObjectRecordsWithValue() { - // Setup - Account newAccount1 = getAccount(1); - Account newAccount2 = getAccount(2); - - List newAccounts = new List{ newAccount1, newAccount2 }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toInsert(DML.Records(newAccounts).with(Account.Industry, 'New Test Industry').with(Account.Description, 'New Test Description')).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); - - List accounts = [SELECT Id, Industry, Description FROM Account LIMIT 2]; - - Assert.areEqual('New Test Industry', accounts[0].Industry, 'Account industry should be "New Test Industry".'); - Assert.areEqual('New Test Description', accounts[0].Description, 'Account description should be "New Test Description".'); - - Assert.areEqual('New Test Industry', accounts[1].Industry, 'Account industry should be "New Test Industry".'); - Assert.areEqual('New Test Description', accounts[1].Description, 'Account description should be "New Test Description".'); - } - - @IsTest - static void retrieveResultForWhenIncorrectIdentifierIsUsed() { - // Setup - Exception expectedException = null; - - // Test - Test.startTest(); - try { - DML.retrieveResultFor('dmlMockId'); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('No result found for dml identifier: dmlMockId', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void mockAllDmls() { - // Setup - Account account1 = getAccount(1); - - Contact contact1 = getContact(1); - contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); - - Opportunity opportunity1 = getOpportunity(1); - opportunity1.Id = DML.randomIdGenerator.get(Opportunity.SObjectType); - - DML.mock('dmlMockId').allDmls(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toInsert(account1).toUpdate(contact1).toDelete(opportunity1).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Opportunity], 'No records should be deleted from the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); - Assert.areEqual(Opportunity.SObjectType, result.deletesOf(Opportunity.SObjectType).objectType(), 'Deleted operation result should contain Opportunity object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Opportunity.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - } - - // ================================================ RESULTS ================================================= - - @IsTest - static void resultAll() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toInsert(account).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, result.all().size(), 'Result should contain 1 operation result.'); - } - - @IsTest - static void resultInserts() { - // Setup - Account account1 = getAccount(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toInsert(account1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, result.inserts().size(), 'Result should contain 1 insert operation result.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - - DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.isFalse(operationResult.hasFailures(), 'Inserted operation result should not have failures.'); - } - - @IsTest - static void resultUpdates() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - account1.Name = 'Updated Test Account'; - - DML.Result result = new DML().toUpdate(account1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(1, result.updates().size(), 'Result should contain 1 update operation result.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); - Assert.isFalse(operationResult.hasFailures(), 'Updated operation result should not have failures.'); - } - - @IsTest - static void resultUpserts() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - account1.Name = 'Updated Test Account'; - - DML.Result result = new DML().toUpsert(account1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(1, result.upserts().size(), 'Result should contain 1 upsert operation result.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - - DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.isFalse(operationResult.hasFailures(), 'Upserted operation result should not have failures.'); - } - - @IsTest - static void resultDeletes() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toDelete(account1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(1, result.deletes().size(), 'Result should contain 1 delete operation result.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.isFalse(operationResult.hasFailures(), 'Deleted operation result should not have failures.'); - } - - @IsTest - static void resultUndeletes() { - // Setup - Account account1 = getAccount(1); - insert account1; - delete account1; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toUndelete(account1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(1, result.undeletes().size(), 'Result should contain 1 undelete operation result.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - - DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.isFalse(operationResult.hasFailures(), 'Undeleted operation result should not have failures.'); - } - - @IsTest - static void resultEvents() { - // Setup - FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - DML.Result result = new DML().toPublish(event1).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(1, result.events().size(), 'Result should contain 1 publish operation result.'); - - DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Published operation result should contain publish type.'); - } - - @IsTest - static void resultWhenFailure() { - // Setup - Account account1 = getAccount(1); - insert account1; - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - account1.Name = ''; // Name is required - - new DML().toUpdate(account1).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Required fields are missing: [Name]'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void resultWhenFailureButAllowPartialSuccess() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - account1.Name = ''; // Name is required - - DML.Result result = new DML().toUpdate(account1).allowPartialSuccess().commitWork(); - Test.stopTest(); - - // Verify - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(0, operationResult.successes().size(), 'Updated operation result should contain 0 success record.'); - Assert.areEqual(1, operationResult.failures().size(), 'Updated operation result should contain 1 failure record.'); - - Assert.areEqual(1, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(1, operationResult.errors().size(), 'Updated operation result should contain 1 error.'); - - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); - Assert.isTrue(operationResult.hasFailures(), 'Updated operation result should have failures.'); - - DML.RecordResult recordResult = operationResult.recordResults()[0]; - - Assert.areEqual(account1.Id, recordResult.id(), 'Record result should contain the account id.'); - Assert.areEqual(account1, recordResult.record(), 'Record result should contain the account record.'); - Assert.isFalse(recordResult.isSuccess(), 'Record result should not be successful.'); - Assert.areEqual(1, recordResult.errors().size(), 'Record result should contain 1 error.'); - - DML.Error error = recordResult.errors()[0]; - - Assert.areEqual('Required fields are missing: [Name]', error.message(), 'Record result should contain the error message.'); - Assert.areEqual(System.StatusCode.REQUIRED_FIELD_MISSING, error.statusCode(), 'Record result should contain the error status code.'); - Assert.isTrue(error.fields().contains('Name'), 'Record result should contain the error fields.'); - } - - @IsTest - static void resultMerges() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - insert new List{ masterAccount, duplicateAccount }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().toMerge(masterAccount, duplicateAccount).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - Assert.areEqual(1, result.merges().size(), 'Result should contain 1 merge operation result.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); - Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); - } - - @IsTest - static void resultWhenNoOperations() { - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML().commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be executed.'); - Assert.areEqual(0, result.all().size(), 'Result should contain 0 operation results.'); - Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); - Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); - Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); - Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); - Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); - Assert.areEqual(0, result.merges().size(), 'Result should contain 0 merge operation results.'); - Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); - } - - // ================================================ DEPENDENCIES ================================================= - - @IsTest - static void addInRandomOrder() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - Opportunity opportunity1 = getOpportunity(1); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - new DML() - .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)) - .toInsert(DML.Record(opportunity1).withRelationship(Opportunity.AccountId, account1)) - .toInsert(account1) - .commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue( - expectedException.getMessage().contains('Relationship was registered for a record that has not been registered yet.'), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void selfDependency() { - // Setup - Account account1 = getAccount(1); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - new DML().toInsert(account1).toInsert(DML.Record(account1).withRelationship(Account.ParentId, account1)).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Self-dependency detected for record: ' + account1.Id), 'Expected exception message should be thrown.'); - } - - @IsTest - static void cyclicDependency() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - new DML() - .toInsert(account1) - .toInsert(DML.Record(account2).withRelationship(Account.ParentId, account1)) - .toInsert(DML.Record(account1).withRelationship(Account.ParentId, account2)) - .commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Cyclic dependencies detected among records.'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void registerComplex() { - // Setup - DML uow = new DML(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - for (Integer i = 0; i < 10; i++) { - Opportunity newOpportunity = new Opportunity(Name = 'UoW Test Name ' + i, StageName = 'Open', CloseDate = System.today()); - - uow.toInsert(newOpportunity); - - for (Integer j = 0; j < i + 1; j++) { - Product2 product = new Product2(Name = newOpportunity.Name + ' : Product : ' + i); - - uow.toInsert(product); - - PricebookEntry pbe = new PricebookEntry(UnitPrice = 10, IsActive = true, UseStandardPrice = false, Pricebook2Id = Test.getStandardPricebookId()); - - uow.toInsert(DML.Record(pbe).withRelationship(PricebookEntry.Product2Id, product)); - - uow.toInsert( - DML.Record(new OpportunityLineItem(Quantity = 1, TotalPrice = 10)) - .withRelationship(OpportunityLineItem.PricebookEntryId, pbe) - .withRelationship(OpportunityLineItem.OpportunityId, newOpportunity) - ); - } - } - uow.commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(4, dmlsAfter - dmlsBefore, '4 DML statements should be executed (Opportunity, Product2, PricebookEntry, OpportunityLineItem).'); - Assert.areEqual(10, [SELECT COUNT() FROM Opportunity], 'Opportunities should be inserted.'); - Assert.areEqual(55, [SELECT COUNT() FROM Product2], 'Products should be inserted.'); - Assert.areEqual(55, [SELECT COUNT() FROM PricebookEntry], 'Pricebook entries should be inserted.'); - } - - @IsTest - static void insertUpdateAndDeleteWithRelationships() { - // Setup - Account existingAccount = getAccount(1); - insert existingAccount; - - Account newAccount = getAccount(2); - Contact contact1 = getContact(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML() - .toInsert(newAccount) - .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, newAccount)) - .toUpdate(DML.Record(existingAccount).with(Account.Description, 'Marked for review')) - .toDelete(existingAccount) - .commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(4, dmlsAfter - dmlsBefore, '4 DML statements should be executed (Account insert, Contact insert, Account update, Account delete).'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only the new account should exist.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted.'); - Assert.areEqual(newAccount.Id, [SELECT AccountId FROM Contact WHERE Id = :contact1.Id].AccountId, 'Contact should be linked to the new Account.'); - Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 2 object types.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 object type.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 object type.'); - } - - @IsTest - static void multiLevelRelationshipHierarchy() { - // Setup - Account grandParentAccount = new Account(Name = 'Grand Parent Account'); - Account parentAccount = new Account(Name = 'Parent Account'); - Account childAccount = new Account(Name = 'Child Account'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML() - .toInsert(grandParentAccount) - .toInsert(DML.Record(parentAccount).withRelationship(Account.ParentId, grandParentAccount)) - .toInsert(DML.Record(childAccount).withRelationship(Account.ParentId, parentAccount)) - .commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed (one per hierarchy level due to dependencies).'); - Assert.areEqual(3, [SELECT COUNT() FROM Account], 'All accounts should be inserted.'); - - Account queriedChild = [SELECT ParentId, Parent.ParentId FROM Account WHERE Id = :childAccount.Id]; - Assert.areEqual(parentAccount.Id, queriedChild.ParentId, 'Child should be linked to parent.'); - Assert.areEqual(grandParentAccount.Id, queriedChild.Parent.ParentId, 'Parent should be linked to grandparent.'); - } - - @IsTest - static void multipleDependentObjectTypesWithRelationships() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - Opportunity opportunity1 = getOpportunity(1); - Case case1 = new Case(Subject = 'Test Case 1', Status = 'New'); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Result result = new DML() - .toInsert(account1) - .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)) - .toInsert(DML.Record(opportunity1).withRelationship(Opportunity.AccountId, account1)) - .toInsert(DML.Record(case1).withRelationship(Case.AccountId, account1).withRelationship(Case.ContactId, contact1)) - .commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(4, dmlsAfter - dmlsBefore, '4 DML statements should be executed (Account, Contact, Opportunity, Case).'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be inserted.'); - Assert.areEqual(1, [SELECT COUNT() FROM Case], 'Case should be inserted.'); - - Case queriedCase = [SELECT AccountId, ContactId FROM Case WHERE Id = :case1.Id]; - Assert.areEqual(account1.Id, queriedCase.AccountId, 'Case should be linked to Account.'); - Assert.areEqual(contact1.Id, queriedCase.ContactId, 'Case should be linked to Contact.'); - Assert.areEqual(4, result.inserts().size(), 'Inserted operation result should contain 4 object types.'); - } - - @IsTest - static void insertWithRelationshipsAndImmediateOperations() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - Contact contact2 = getContact(2); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML db = new DML(); - - // Insert account immediately - db.insertImmediately(account1); - - // Register contact with relationship to the already-inserted account - db.toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)).toInsert(DML.Record(contact2).withRelationship(Contact.AccountId, account1)).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed (Account immediate, Contacts commit).'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.areEqual(2, [SELECT COUNT() FROM Contact], 'Contacts should be inserted.'); - Assert.areEqual(account1.Id, [SELECT AccountId FROM Contact WHERE Id = :contact1.Id].AccountId, 'Contact 1 should be linked to Account.'); - Assert.areEqual(account1.Id, [SELECT AccountId FROM Contact WHERE Id = :contact2.Id].AccountId, 'Contact 2 should be linked to Account.'); - } - - @IsTest - static void upsertAndInsertWithRelationships() { - // Setup - Account existingAccount = getAccount(1); - insert existingAccount; - - Account newAccount = getAccount(2); - Contact contact1 = getContact(1); - Contact contact2 = getContact(2); - - existingAccount.Description = 'Updated Description'; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML() - .toUpsert(existingAccount) // Update existing - .toInsert(newAccount) // Insert new - .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, existingAccount)) - .toInsert(DML.Record(contact2).withRelationship(Contact.AccountId, newAccount)) - .commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed (Account upsert, Account insert, Contact insert).'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Both accounts should exist.'); - Assert.areEqual(2, [SELECT COUNT() FROM Contact], 'Contacts should be inserted.'); - Assert.areEqual('Updated Description', [SELECT Description FROM Account WHERE Id = :existingAccount.Id].Description, 'Existing account should be updated.'); - Assert.areEqual(existingAccount.Id, [SELECT AccountId FROM Contact WHERE Id = :contact1.Id].AccountId, 'Contact 1 should be linked to existing Account.'); - Assert.areEqual(newAccount.Id, [SELECT AccountId FROM Contact WHERE Id = :contact2.Id].AccountId, 'Contact 2 should be linked to new Account.'); - } - - // ================================================================================================ - // =========================================== MOCKING ============================================ - // ================================================================================================ - - @IsTest - static void complexHierarchyWithMocking() { - // Setup - 3 level hierarchy with multiple branches - Account parentAccount = new Account(Name = 'Parent'); - Account childAccount1 = new Account(Name = 'Child 1'); - Account childAccount2 = new Account(Name = 'Child 2'); - Contact contact1 = getContact(1); - Contact contact2 = getContact(2); - Opportunity opportunity1 = getOpportunity(1); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML() - .identifier('dmlMockId') - .toInsert(parentAccount) - .toInsert(DML.Record(childAccount1).withRelationship(Account.ParentId, parentAccount)) - .toInsert(DML.Record(childAccount2).withRelationship(Account.ParentId, parentAccount)) - .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, childAccount1)) - .toInsert(DML.Record(contact2).withRelationship(Contact.AccountId, childAccount2)) - .toInsert(DML.Record(opportunity1).withRelationship(Opportunity.AccountId, parentAccount)) - .commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(3, result.inserts().size(), 'Inserted operation result should contain 3 object types (Account, Contact, Opportunity).'); - Assert.areEqual(3, result.insertsOf(Account.SObjectType).records().size(), 'Account result should contain 3 records.'); - Assert.areEqual(2, result.insertsOf(Contact.SObjectType).records().size(), 'Contact result should contain 2 records.'); - Assert.areEqual(1, result.insertsOf(Opportunity.SObjectType).records().size(), 'Opportunity result should contain 1 record.'); - } - - // ================================================ MOCKING - INSERT ================================================= - - @IsTest - static void toInsertSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - } - - @IsTest - static void toInsertSingleRecordWithMockingAndRelationship() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - - DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, accountResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult contactResult = result.insertsOf(Contact.SObjectType); - - Assert.areEqual(Contact.SObjectType, contactResult.objectType(), 'Inserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, contactResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, contactResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, contactResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(contactResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(contactResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); - } - - @IsTest - static void toInsertMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).toInsert(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(2, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted records.'); - Assert.areEqual(2, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[1].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - } - - @IsTest - static void toInsertMultipleRecordsWithMockingSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - DML.mock('dmlMockId').insertsFor(Account.SObjectType); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).toInsert(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed for contact which is not mocked.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted, because it was mocked.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted, because only Account was mocked.'); - Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.insertsOf(Contact.SObjectType).objectType(), 'Inserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Contact.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(1, result.insertsOf(Contact.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.insertsOf(Contact.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isTrue(result.insertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.insertsOf(Contact.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); - } - - @IsTest - static void toInsertMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(account1).toInsert(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.insertsOf(Contact.SObjectType).objectType(), 'Inserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Contact.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.areEqual(1, result.insertsOf(Contact.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, result.insertsOf(Contact.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isTrue(result.insertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.insertsOf(Contact.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); - } - - @IsTest - static void toInsertWithEmptyRecordWhenMocking() { - // Setup - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toInsert(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - @IsTest - static void toInsertWithMockingException() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnInserts(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toInsertWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnInserts(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toInsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Result should contain insert type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toInsertWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnInsertsFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toInsertWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnInsertsFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toInsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Result should contain insert type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - // ================================================ MOCKING - UPDATE ================================================= - - @IsTest - static void toUpdateSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account'; - - new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUpdateMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account 1'; - account2.Name = 'Updated Test Account 2'; - - new DML().toUpdate(account1).toUpdate(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(2, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated records.'); - Assert.areEqual(2, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[1].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); - } - - @IsTest - static void toUpdateMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); - - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account 1'; - contact1.FirstName = 'Updated Test Contact 1'; - - new DML().toUpdate(account1).toUpdate(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(2, result.updates().size(), 'Updated operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(1, result.updatesOf(Contact.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.updatesOf(Contact.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isTrue(result.updatesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.isNotNull(result.updatesOf(Contact.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Updated Test Contact 1', contact1.FirstName, 'Contact 1 should be updated.'); - } - - @IsTest - static void toUpdateMultipleRecordsWithMockingSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - insert account1; - - Contact contact1 = getContact(1); - insert contact1; - - DML.mock('dmlMockId').updatesFor(Account.SObjectType); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Test Account 1'; - contact1.FirstName = 'Updated Test Contact 1'; - - new DML().toUpdate(account1).toUpdate(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should not be updated in the database.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should not be updated in the database.'); - Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); - Assert.areEqual(2, result.updates().size(), 'Updated operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.areEqual(1, result.updatesOf(Contact.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, result.updatesOf(Contact.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isTrue(result.updatesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.isNotNull(result.updatesOf(Contact.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUpdateWithMockingException() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUpdates(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUpdateWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUpdates(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUpdate(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Result should contain update type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUpdateWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUpdatesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUpdateWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUpdatesFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUpdate(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Result should contain update type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUpdateWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toUpdate(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 1 result.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ MOCKING - UPSERT ================================================= - - @IsTest - static void toUpsertSingleRecordWhenIdIsNotSpecifiedWithMocking() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account'; - - new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - } - - @IsTest - static void toUpsertSingleRecordWhenIdIsSpecifiedWithMocking() { - // Setup - Account account1 = getAccount(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account'; - - new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - } - - @IsTest - static void toUpsertMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account 1'; - account2.Name = 'Upserted Test Account 2'; - - new DML().toUpsert(account1).toUpsert(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(2, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(2, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[1].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be upserted.'); - Assert.areEqual('Upserted Test Account 2', account2.Name, 'Account 2 should be upserted.'); - } - - @IsTest - static void toUpsertMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account 1'; - contact1.FirstName = 'Upserted Test Contact 1'; - - new DML().toUpsert(account1).toUpsert(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); - Assert.areEqual(2, result.upserts().size(), 'Upserted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.upsertsOf(Contact.SObjectType).objectType(), 'Upserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Contact.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isTrue(result.upsertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.upsertsOf(Contact.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be updated.'); - Assert.areEqual('Upserted Test Contact 1', contact1.FirstName, 'Contact 1 should be updated.'); - } - - @IsTest - static void toUpsertMultipleRecordsWithMockingSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - insert account1; - - Contact contact1 = getContact(1); - - DML.mock('dmlMockId').upsertsFor(Contact.SObjectType); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - account1.Name = 'Upserted Test Account 1'; - contact1.FirstName = 'Upserted Test Contact 1'; - - new DML().toUpsert(account1).toUpsert(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should not be updated in the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'Contact should be upserted in the database.'); - Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); - Assert.areEqual(2, result.upserts().size(), 'Upserted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.upsertsOf(Contact.SObjectType).objectType(), 'Upserted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Contact.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isTrue(result.upsertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.upsertsOf(Contact.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUpsertWithMockingException() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnUpserts(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUpsertWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnUpserts(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUpsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Result should contain upsert type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUpsertWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnUpsertsFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUpsertWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').exceptionOnUpsertsFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUpsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Result should contain upsert type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUpsertWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toUpsert(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ MOCKING - DELETE ================================================= - - @IsTest - static void toDeleteSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toDelete(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteSingleRecordByIdWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toDelete(account1.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toDelete(account1).toDelete(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteMultipleRecordsByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toDelete(new List{ account1.Id, account2.Id }).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toDelete(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - @IsTest - static void toDeleteMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toDelete(account1).toDelete(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should exist, because delete was mocked.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No Contact records should exist, because delete was mocked.'); - Assert.areEqual(2, result.deletes().size(), 'Deleted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.deletesOf(Contact.SObjectType).objectType(), 'Deleted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Contact.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(1, result.deletesOf(Contact.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Contact.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(result.deletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.deletesOf(Contact.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteMultipleRecordsWithMockingSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - insert account1; - - Contact contact1 = getContact(1); - insert contact1; - - DML.mock('dmlMockId').deletesFor(Account.SObjectType); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toDelete(account1).toDelete(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should not be deleted from the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'Contact should be deleted from the database.'); - Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); - Assert.areEqual(2, result.deletes().size(), 'Deleted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.deletesOf(Contact.SObjectType).objectType(), 'Deleted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Contact.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.areEqual(1, result.deletesOf(Contact.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Contact.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(result.deletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.deletesOf(Contact.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteWithMockingException() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletes(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toDelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toDeleteWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletes(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toDelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Result should contain delete type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toDeleteWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toDelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toDeleteWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toDelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Result should contain delete type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - // ================================================ MOCKING - HARD DELETE ================================================= - - @IsTest - static void toHardDeleteSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toHardDeleteMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toHardDelete(account1).toHardDelete(account2).identifier('dmlMockId').commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toHardDeleteByIdWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toHardDelete(account1.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toHardDeleteByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toHardDelete(new Set{ account1.Id, account2.Id }).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toHardDeleteWithMockingException() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletes(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toHardDeleteWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toHardDeleteWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toHardDelete(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ MOCKING - UNDELETE ================================================= - - @IsTest - static void toUndeleteSingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toUndelete(account1).toUndelete(account2).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(2, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteByIdWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toUndelete(account1.Id).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').toUndelete(new List{ account1.Id, account2.Id }).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteMultipleRecordTypesWithMocking() { - // Setup - Account account1 = getAccount(1); - Contact contact1 = getContact(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toUndelete(account1).toUndelete(contact1).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should exist, because undelete was mocked.'); - Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No Contact records should exist, because undelete was mocked.'); - Assert.areEqual(2, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.undeletesOf(Contact.SObjectType).objectType(), 'Undeleted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Contact.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(result.undeletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.undeletesOf(Contact.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteMultipleRecordsWithMockingSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - insert account1; - delete account1; - - Contact contact1 = getContact(1); - insert contact1; - delete contact1; - - DML.mock('dmlMockId').undeletesFor(Account.SObjectType); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toUndelete(account1).toUndelete(contact1).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be undeleted in the database.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be undeleted in the database.'); - Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); - Assert.areEqual(2, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(Contact.SObjectType, result.undeletesOf(Contact.SObjectType).objectType(), 'Undeleted operation result should contain Contact object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Contact.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(result.undeletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(result.undeletesOf(Contact.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteWithMockingException() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUndeletes(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUndeleteWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUndeletes(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUndelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Result should contain undelete type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUndeleteWithMockingExceptionForSpecificSObjectType() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUndeletesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toUndeleteWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnUndeletesFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toUndelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Result should contain undelete type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toUndeleteWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toUndelete(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); - } - - // ================================================ MOCKING - MERGE ================================================= - - @IsTest - static void toMergeSingleRecordWithMocking() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allMerges(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be in the database.'); - Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); - Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Account.SObjectType).operationType(), 'Merged operation result should contain merge type.'); - Assert.areEqual(1, result.mergesOf(Account.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, result.mergesOf(Account.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.isTrue(result.mergesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); - Assert.isNotNull(result.mergesOf(Account.SObjectType).recordResults()[0].id(), 'Merged operation result should contain a mocked record Id.'); - } - - @IsTest - static void toMergeMultipleRecordTypesWithMocking() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - Lead masterLead = getLead(1); - Lead duplicateLead = getLead(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - masterLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); - duplicateLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); - - DML.mock('dmlMockId').allMerges(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toMerge(masterAccount, duplicateAccount).toMerge(masterLead, duplicateLead).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should be in the database.'); - Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'No Lead records should be in the database.'); - Assert.areEqual(2, result.merges().size(), 'Merged operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(Lead.SObjectType, result.mergesOf(Lead.SObjectType).objectType(), 'Merged operation result should contain Lead object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Account.SObjectType).operationType(), 'Merged operation result should contain merge type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Lead.SObjectType).operationType(), 'Merged operation result should contain merge type.'); - Assert.areEqual(1, result.mergesOf(Account.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, result.mergesOf(Account.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.areEqual(1, result.mergesOf(Lead.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); - Assert.areEqual(1, result.mergesOf(Lead.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); - Assert.isTrue(result.mergesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); - Assert.isTrue(result.mergesOf(Lead.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); - } - - @IsTest - static void toMergeWithMockingSpecificSObjectType() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - insert new List{ masterAccount, duplicateAccount }; - - Lead masterLead = getLead(1); - Lead duplicateLead = getLead(2); - - masterLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); - duplicateLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); - - DML.mock('dmlMockId').mergesFor(Lead.SObjectType); - - // Test - Test.startTest(); - new DML().toMerge(masterAccount, duplicateAccount).toMerge(masterLead, duplicateLead).identifier('dmlMockId').commitWork(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account merge should not be mocked.'); - Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'Lead merge should be mocked.'); - Assert.areEqual(2, result.merges().size(), 'Merged operation result should contain 2 results.'); - Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); - Assert.areEqual(Lead.SObjectType, result.mergesOf(Lead.SObjectType).objectType(), 'Merged operation result should contain Lead object type.'); - } - - @IsTest - static void toMergeWithMockingException() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnMerges(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toMergeWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnMerges(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toMerge(masterAccount, duplicateAccount).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Result should contain merge type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toMergeWithMockingExceptionForSpecificSObjectType() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnMergesFor(Account.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toMergeWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').exceptionOnMergesFor(Account.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toMerge(masterAccount, duplicateAccount).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); - Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Result should contain merge type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toMergeWithEmptyRecordsWhenMocking() { - // Setup - Account masterAccount = getAccount(1); - masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allMerges(); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toMerge(masterAccount, new List()).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify); - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - } - - // ================================================ MOCKING - PUBLISH ================================================= - - @IsTest - static void toPublishSingleRecordWithMocking() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toPublish(event).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - } - - @IsTest - static void toPublishSingleRecordWithMockingSpecificSObjectType() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').publishesFor(FlowOrchestrationEvent.SObjectType); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toPublish(event).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - } - - @IsTest - static void toPublishMultipleRecordsWithMocking() { - // Setup - FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); - FlowOrchestrationEvent event2 = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toPublish(event1).toPublish(event2).identifier('dmlMockId').commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published records.'); - Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].id(), 'Published operation result should contain a mocked record Id.'); - } - - @IsTest - static void toPublishWithMockingException() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').exceptionOnPublishes(); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toPublish(event).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toPublishWithMockingExceptionWhenAllOrNoneIsSet() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').exceptionOnPublishes(); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toPublish(event).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Result should contain publish type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toPublishWithMockingExceptionForSpecificSObjectType() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').exceptionOnPublishesFor(FlowOrchestrationEvent.SObjectType); - - Exception expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toPublish(event).identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - @IsTest - static void toPublishWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').exceptionOnPublishesFor(FlowOrchestrationEvent.SObjectType); - - Exception expectedException = null; - DML.Result result = null; - - // Test - Test.startTest(); - try { - result = new DML().toPublish(event).allowPartialSuccess().identifier('dmlMockId').commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNull(expectedException, 'Expected exception to not be thrown.'); - - DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); - - Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); - Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); - Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Result should contain FlowOrchestrationEvent object type.'); - Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Result should contain publish type.'); - Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); - Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); - } - - @IsTest - static void toPublishWithEmptyRecordsWhenMocking() { - // Setup - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - new DML().toPublish(new List()).identifier('dmlMockId').commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); - Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); - Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); - Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); - Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); - Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); - Assert.areEqual(0, result.events().size(), 'Published operation result should contain 1 result.'); - } - - // ================================================ MOCKING - INSERT IMMEDIATELY ================================================= - - @IsTest - static void toInsertImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').insertImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(1, accountResult.records().size(), 'Inserted operation result should contain the inserted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toInsertImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').insertImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); - - Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Inserted operation result should contain the inserted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); - } - - // ================================================ MOCKING - UPSERT IMMEDIATELY ================================================= - - @IsTest - static void toUpsertImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').upsertImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted to the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPSERT_DML, accountResult.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(1, accountResult.records().size(), 'Upserted operation result should contain the upserted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUpsertImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - DML.mock('dmlMockId').allUpserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').upsertImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted to the database.'); - Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.upsertsOf(Account.SObjectType); - - Assert.areEqual(DML.OperationType.UPSERT_DML, accountResult.operationType(), 'Upserted operation result should contain upsert type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Upserted operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Upserted operation result should contain the upserted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Upserted operation result should contain a mocked record Id.'); - } - - // MOCKING - UPDATE IMMEDIATELY - - @IsTest - static void toUpdateImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').updateImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated to the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UPDATE_DML, accountResult.operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(1, accountResult.records().size(), 'Updated operation result should contain the updated record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUpdateImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUpdates(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').updateImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated to the database.'); - Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.updatesOf(Account.SObjectType); - - Assert.areEqual(DML.OperationType.UPDATE_DML, accountResult.operationType(), 'Updated operation result should contain update type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Updated operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Updated operation result should contain the updated records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Updated operation result should contain a mocked record Id.'); - } - - // ================================================ MOCKING - DELETE IMMEDIATELY ================================================= - - @IsTest - static void toDeleteImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').deleteImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteImmediatelyByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').deleteImmediately(account1.Id); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').deleteImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toDeleteImmediatelyMultipleRecordsByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allDeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').deleteImmediately(new List{ account1.Id, account2.Id }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); - Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); - } - - // ================================================ MOCKING - UNDELETE IMMEDIATELY ================================================= - - @IsTest - static void toUndeleteImmediatelySingleRecordWithMocking() { - // Setup - Account account1 = getAccount(1); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').undeleteImmediately(account1); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void toUndeleteImmediatelyMultipleRecordsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').undeleteImmediately(new List{ account1, account2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); - Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void undeleteImmediatelyByIdWithMocking() { - // Setup - Account account1 = getAccount(1); - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').undeleteImmediately(account1.Id); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); - Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); - Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - @IsTest - static void undeleteImmediatelyByIdsWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - account1.Id = DML.randomIdGenerator.get(Account.SObjectType); - account2.Id = DML.randomIdGenerator.get(Account.SObjectType); - - DML.mock('dmlMockId').allUndeletes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').undeleteImmediately(new List{ account1.Id, account2.Id }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); - Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); - Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); - - DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); - - Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); - Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); - Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); - Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); - Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); - Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); - Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); - } - - // ================================================ MOCKING - PUBLISH IMMEDIATELY ================================================= - - @IsTest - static void publishImmediatelySingleRecordWithMocking() { - // Setup - FlowOrchestrationEvent event = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').publishImmediately(event); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); - Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - } - - @IsTest - static void publishImmediatelyMultipleRecordsWithMocking() { - // Setup - FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); - FlowOrchestrationEvent event2 = new FlowOrchestrationEvent(); - - DML.mock('dmlMockId').allPublishes(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().identifier('dmlMockId').publishImmediately(new List{ event1, event2 }); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); - Assert.areEqual( - FlowOrchestrationEvent.SObjectType, - result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), - 'Published operation result should contain FlowOrchestrationEvent object type.' - ); - Assert.areEqual( - DML.OperationType.PUBLISH_DML, - result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), - 'Published operation result should contain publish type.' - ); - Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published records.'); - Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].isSuccess(), 'Published operation result should contain a successful record result.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); - Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].id(), 'Published operation result should contain a mocked record Id.'); - } - - // ================================================ MOCKING - OTHERS ================================================= - - @IsTest - static void retrieveResultForWhichDoesNotExist() { - // Setup - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - DML.retrieveResultFor('dmlMockId'); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('No result found for dml identifier: dmlMockId'), 'Expected exception message should be thrown.'); - } - - // ================================================ OPTIONS ================================================= - - @IsTest - static void sharedDmlInstance() { - Account account = getAccount(1); - - Test.startTest(); - Integer dmlStatementsBefore = Limits.getDMLStatements(); - - DML.Shared.toInsert(account).commitWork(); - DML.Shared.commitWork(); - - Integer dmlStatementsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); - Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); - } - - @IsTest - static void sharedDmlInstanceWithMocking() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - DML.mock('dmlMockId').allInserts(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Shared.identifier('dmlMockId').toInsert(account1).toInsert(account2).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - DML.Result result = DML.retrieveResultFor('dmlMockId'); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); - Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); - Assert.areEqual(2, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain 2 records.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); - Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Inserted operation result should contain a successful record result.'); - } - - @IsTest - static void sharedDmlInstanceMultipleOperations() { - // Setup - Account account1 = getAccount(1); - insert account1; - - Account account2 = getAccount(2); - Contact contact1 = getContact(1); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - account1.Name = 'Updated Account'; - - DML.Shared.toInsert(account2).toUpdate(account1).toInsert(contact1).commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed (2 inserts + 1 update).'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Two accounts should exist.'); - Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'One contact should exist.'); - Assert.areEqual('Updated Account', [SELECT Name FROM Account WHERE Id = :account1.Id].Name, 'Account 1 should be updated.'); - } - - @IsTest - static void sharedDmlInstanceDiscardWork() { - // Setup - Account account1 = getAccount(1); - Account account2 = getAccount(2); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - DML.Shared.toInsert(account1).toInsert(account2).discardWork(); - DML.Shared.commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be executed, because work was discarded.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No accounts should be inserted, because work was discarded.'); - Assert.isNull(account1.Id, 'Account 1 should not have an Id.'); - Assert.isNull(account2.Id, 'Account 2 should not have an Id.'); - } - - @IsTest - static void allowFieldTruncationOption() { - // Setup - String longAccountName = 'Test Account ' + 'Test'.repeat(' ', 100); - Account account = getAccount(1); - account.Name = longAccountName; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - Database.DmlOptions options = new Database.DmlOptions(); - options.allowFieldTruncation = true; - - new DML().toInsert(account).options(options).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - List accounts = getAccounts(); - - Assert.areEqual(1, accounts.size(), 'Account should be inserted.'); - Assert.areEqual(255, accounts[0].Name.length(), 'Account name should be 255 characters long, because allowFieldTruncation is true.'); - } - - @IsTest - static void optionsAllOrNoneDisabled() { - // Setup - List accounts = new List{ - getAccount(1), - new Account() // Name is required - }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - Database.DmlOptions options = new Database.DmlOptions(); - options.optAllOrNone = false; - - new DML().toInsert(accounts).options(options).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only one account should be inserted, because one has missing required field.'); - } - - @IsTest - static void skipDuplicateRules() { - // Setup - Account account = getAccount(1); - Account duplicateAccount = new Account(Name = account.Name); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toInsert(account).toInsert(duplicateAccount).skipDuplicateRules().commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Both accounts should be inserted, because skipDuplicateRules is true.'); - } - - @IsTest - static void optionsAllOrNoneEnabled() { - // Setup - List accounts = new List{ - getAccount(1), - new Account() // Name is required - }; - - Exception expectedException = null; - - // Test - Test.startTest(); - Database.DmlOptions options = new Database.DmlOptions(); - options.optAllOrNone = true; - - try { - new DML().toInsert(accounts).options(options).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Required fields are missing: [Name]'), 'Expected exception message should be thrown.'); - } - - // ================================================ OPTIONS - ALLOW PARTIAL SUCCESS ================================================= - - @IsTest - static void toInsertWithPartialSuccess() { - // Setup - List accounts = new List{ - getAccount(1), - new Account() // Name is required - }; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - new DML().toInsert(accounts).allowPartialSuccess().commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only one account should be inserted, because one has missing required field.'); - } - - @IsTest - static void toUpdateWithPartialSuccess() { - // Setup - List accounts = new List{ getAccount(1), getAccount(2) }; - insert accounts; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - accounts[0].Name = null; - accounts[1].Name = 'Test Account 1 New Name'; - - new DML().toUpdate(accounts).allowPartialSuccess().commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the partial success.'); - Assert.areEqual( - 2, - [SELECT COUNT() FROM Account WHERE Name IN ('Test Account 1', 'Test Account 1 New Name')], - 'Accounts should be present with expected names after partial success update.' - ); - } - - @IsTest - static void toUpsertWithPartialSuccess() { - // Setup - List accounts = new List{ getAccount(1), getAccount(2) }; - insert accounts; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - - accounts[0].Name = null; // Name will not change, because it's set to null - accounts[1].Name = 'Test Account 1 New Name'; - - accounts.add(getAccount(3)); // New account - - new DML().toUpsert(accounts).allowPartialSuccess().commitWork(); - - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual( - 3, - [SELECT COUNT() FROM Account WHERE Name IN ('Test Account 1', 'Test Account 1 New Name', 'Test Account 3')], - 'Accounts should be upserted with expected names.' - ); - } - - @IsTest - static void toDeleteWithPartialSuccess() { - // Setup - List accounts = new List{ getAccount(1), getAccount(2) }; - insert accounts; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toDelete(accounts).allowPartialSuccess().commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); - } - - @IsTest - static void toHardDeleteWithPartialSuccess() { - // Setup - List accounts = new List{ getAccount(1), getAccount(2) }; - - insert accounts; - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toHardDelete(accounts).allowPartialSuccess().commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed (1 delete + 1 hard delete).'); - Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be hard deleted.'); - // No assertion with ALL ROWS, because there is Salesforce error - } - - // ================================================ OPTIONS - USER MODE ================================================= - - @IsTest - static void toInsertUserMode() { - // Setup - Case newCase = getCase(1); - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toInsert(newCase).commitWork(); // user mode by default - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithUserModeExplicitlySet() { - // Setup - Case newCase = getCase(1); - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toInsert(newCase).userMode().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toUpdateWithUserMode() { - // Setup - Case newCase = getCase(1); - insert newCase; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - newCase.Subject = 'Updated Test Case'; - new DML().toUpdate(newCase).commitWork(); // user mode by default - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toUpdateWithUserModeExplicitlySet() { - // Setup - Case newCase = getCase(1); - insert newCase; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - newCase.Subject = 'Updated Test Case'; - new DML().toUpdate(newCase).userMode().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toUpsertWithUserMode() { - // Setup - Case newCase1 = getCase(1); - Case newCase2 = getCase(2); - - insert newCase1; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - newCase1.Subject = 'Updated Test Case'; - - new DML().toUpsert(newCase1).toUpsert(newCase2).commitWork(); // user mode by default - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue( - expectedException.getMessage().contains('Operation failed due to fields being inaccessible on Sobject Case, check errors on Exception or Result'), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void toUpsertWithUserModeExplicitlySet() { - // Setup - Case newCase1 = getCase(1); - Case newCase2 = getCase(2); - - insert newCase1; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - newCase1.Subject = 'Updated Test Case'; - - new DML().toUpsert(newCase1).toUpsert(newCase2).userMode().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue( - expectedException.getMessage().contains('Operation failed due to fields being inaccessible on Sobject Case, check errors on Exception or Result'), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void toDeleteWithUserMode() { - // Setup - Case newCase = getCase(1); - insert newCase; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toDelete(newCase).commitWork(); // user mode by default - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toDeleteWithUserModeExplicitlySet() { - // Setup - Case newCase = getCase(1); - insert newCase; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toDelete(newCase).userMode().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toHardDeleteWithUserMode() { - // Setup - Case newCase = getCase(1); - insert newCase; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toHardDelete(newCase).commitWork(); // user mode by default - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toHardDeleteWithSystemMode() { - // Setup - Case newCase = getCase(1); - insert newCase; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - new DML().toHardDelete(newCase).systemMode().withoutSharing().commitWork(); - } - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be hard deleted.'); - // No assertion with ALL ROWS, because there is Salesforce error - } - - @IsTest - static void toUndeleteWithUserMode() { - // Setup - Case newCase = getCase(1); - insert newCase; - - delete newCase; - - Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toUndelete(newCase).commitWork(); // user mode by default - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('invalid record id'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toUndeleteWithUserModeExplicitlySet() { - // Setup - Case newCase = getCase(1); - insert newCase; - - delete newCase; - - Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toUndelete(newCase).userMode().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('invalid record id'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toMergeWithUserMode() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - insert new List{ masterAccount, duplicateAccount }; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toMerge(masterAccount, duplicateAccount).commitWork(); // user mode by default - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - } - - // ================================================ OPTIONS - SYSTEM MODE ================================================= - - @IsTest - static void toInsertSystemMode() { - // Setup - Case newCase = getCase(1); - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - new DML().toInsert(newCase).systemMode().commitWork(); - } - Test.stopTest(); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Case], 'Case should be inserted.'); - Assert.isNotNull(newCase.Id, 'Case should be inserted and have an Id.'); - } - - @IsTest - static void toUpdateWithSystemMode() { - // Setup - Case newCase = getCase(1); - insert newCase; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - newCase.Subject = 'Updated Test Case'; - - new DML().toUpdate(newCase).systemMode().withoutSharing().commitWork(); - } - Test.stopTest(); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Case], 'Case should be updated.'); - Assert.isNotNull(newCase.Id, 'Case should be updated and have an Id.'); - } - - @IsTest - static void toUpdateWithSystemModeAndWithSharing() { - // Setup - Contact newContact = getContact(1); - insert newContact; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - newContact.FirstName = 'Updated Test Contact'; - new DML().toUpdate(newContact).systemMode().withSharing().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('insufficient access rights on cross-reference id'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toUpsertWithSystemMode() { - // Setup - Case newCase1 = getCase(1); - Case newCase2 = getCase(2); - - insert newCase1; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - newCase1.Subject = 'Updated Test Case'; - - new DML().toUpsert(newCase1).toUpsert(newCase2).systemMode().withoutSharing().commitWork(); - } - Test.stopTest(); - - // Verify - Assert.areEqual(2, [SELECT COUNT() FROM Case], 'Cases should be upserted.'); - - Assert.isNotNull(newCase1.Id, 'Case 1 should be upserted and have an Id.'); - Assert.isNotNull(newCase2.Id, 'Case 2 should be upserted and have an Id.'); - } - - @IsTest - static void toUpsertWithSystemModeAndWithSharing() { - // Setup - Contact newContact1 = getContact(1); - Contact newContact2 = getContact(2); - - insert newContact1; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - newContact1.FirstName = 'Updated Test Contact'; - new DML().toUpsert(newContact1).toUpsert(newContact2).withSharing().systemMode().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('insufficient access rights on cross-reference id'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toDeleteWithSystemMode() { - // Setup - Case newCase = getCase(1); - insert newCase; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - new DML().toDelete(newCase).systemMode().withoutSharing().commitWork(); - } - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); - } - - @IsTest - static void toDeleteWithSystemModeAndWithSharing() { - // Setup - Case newCase = getCase(1); - insert newCase; - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toDelete(newCase).systemMode().withoutSharing().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Case should be deleted with system mode and without sharing.'); - Assert.isNull(expectedException, 'No exception should be thrown.'); - } - - @IsTest - static void toUndeleteWithSystemMode() { - // Setup - Case newCase = getCase(1); - insert newCase; - - delete newCase; - - Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - new DML().toUndelete(newCase).systemMode().withoutSharing().commitWork(); - } - Test.stopTest(); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Case], 'Cases should be undeleted.'); - } - - @IsTest - static void toUndeleteWithSystemModeAndWithSharing() { - // Setup - Case newCase = getCase(1); - insert newCase; - - delete newCase; - - Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); - - Exception expectedException = null; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - try { - new DML().toUndelete(newCase).systemMode().withSharing().commitWork(); - } catch (Exception e) { - expectedException = e; - } - } - Test.stopTest(); - - // Verify - Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Case should not be undeleted, because user has no access with sharing mode.'); - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('insufficient access rights on object id'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toMergeWithSystemMode() { - // Setup - Account masterAccount = getAccount(1); - Account duplicateAccount = getAccount(2); - insert new List{ masterAccount, duplicateAccount }; - - // Test - Test.startTest(); - System.runAs(minimumAccessUser()) { - new DML().toMerge(masterAccount, duplicateAccount).systemMode().withoutSharing().commitWork(); - } - Test.stopTest(); - - // Verify - Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); - } - - // ================================================================================================= - // ================================================ VALIDATION ================================================= - // ================================================================================================= - - // ================================================ VALIDATION - INSERT ================================================= - - @IsTest - static void toInsertWithInvalidRelationshipSingleRecord() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.LastName, newAccount)).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Invalid argument: LastName. Field supplied is not a relationship field.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithInvalidTargetRelationshipFieldSingleRecord() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.LastName, Account.Name, 'Test')).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Invalid argument: relationshipField. Field supplied is not a relationship field.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void recordWithEmptyId() { - // Setup - Account account = getAccount(1); - - // Test - Test.startTest(); - Exception expectedException = null; - try { - new DML().toInsert(DML.Record(account.Id)).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('Invalid argument: recordId. Record ID cannot be null.'), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithInvalidExternalRelationshipSingleRecord() { - // Setup - Account newAccount = getAccount(1); - Contact newContact = getContact(1); - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.AccountId, Account.Name, 'Test')).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void toInsertWithExistingIds() { - // Setup - Account account = getAccount(1); - insert account; - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toInsert(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only new records can be registered as new.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithInvalidTargetRelationshipFieldMultipleRecords() { - // Setup - Account newAccount = getAccount(1); - Contact newContact1 = getContact(1); - Contact newContact2 = getContact(2); - - List contacts = new List{ newContact1, newContact2 }; - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.LastName, newAccount)).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Invalid argument: LastName. Field supplied is not a relationship field.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - @IsTest - static void toInsertWithInvalidExternalRelationshipMultipleRecords() { - // Setup - Account newAccount = getAccount(1); - Contact newContact1 = getContact(1); - Contact newContact2 = getContact(2); - - List contacts = new List{ newContact1, newContact2 }; - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.LastName, Account.Name, 'Test')).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Invalid argument: relationshipField. Field supplied is not a relationship field.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void toInsertWithInvalidExternalIdFieldMultipleRecords() { - // Setup - Account newAccount = getAccount(1); - Contact newContact1 = getContact(1); - Contact newContact2 = getContact(2); - - List contacts = new List{ newContact1, newContact2 }; - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.AccountId, Account.Name, 'Test')).commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - // ================================================ VALIDATION - UPDATE ================================================= - - @IsTest - static void toUpdateSingleRecordTwice() { - // Setup - Account account1 = getAccount(1); - insert account1; - - // Test - Test.startTest(); - Exception expectedException = null; - - try { - account1.Name = 'Updated Test Account'; - - DML db = new DML(); - - db.toUpdate(account1); - - Account duplicatedAccount = new Account(Id = account1.Id, Name = 'Updated Test Account 2'); - - db.toUpdate(duplicatedAccount); - - db.commitWork(); - } catch (Exception e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual( - 'Duplicate records found during registration. Fix the code or use the combineOnDuplicate() method.', - expectedException.getMessage(), - 'Expected exception message should be thrown.' - ); - } - - @IsTest - static void toUpdateWithoutExistingIds() { - // Setup - Account account = getAccount(1); - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUpdate(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only existing records can be updated.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - // ================================================ VALIDATION - UNDELETE ================================================= - - @IsTest - static void toUndeleteWithoutExistingIds() { - // Setup - Account account = getAccount(1); - - DmlException expectedException = null; - - // Test - Test.startTest(); - try { - new DML().toUndelete(account).commitWork(); - } catch (DmlException e) { - expectedException = e; - } - Test.stopTest(); - - // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('Only deleted records can be undeleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); - } - - // ================================================ HOOK ================================================= - - @IsTest - static void commitHook() { - // Setup - Account account = getAccount(1); - - MyHook hook = new MyHook(); - - // Test - Test.startTest(); - Integer dmlsBefore = Limits.getDMLStatements(); - new DML().toInsert(account).commitHook(hook).commitWork(); - Integer dmlsAfter = Limits.getDMLStatements(); - Test.stopTest(); - - // Verify - Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); - Assert.isTrue(hook.beforeCalled, 'Before hook should be called.'); - Assert.isTrue(hook.afterCalled, 'After hook should be called.'); - } - - public class MyHook implements DML.Hook { - private Boolean beforeCalled = false; - private Boolean afterCalled = false; - - public void before() { - beforeCalled = true; - } - - public void after(DML.Result result) { - afterCalled = true; - - Assert.isNotNull(result, 'Result should not be null.'); - } - } - - // HELPERS - - static Account getAccount(Integer index) { - return new Account(Name = 'Test Account ' + index); - } - - static Contact getContact(Integer index) { - return new Contact(FirstName = 'Test ' + index, LastName = 'Contact ' + index); - } - - static Opportunity getOpportunity(Integer index) { - return new Opportunity(Name = 'Test Opportunity ' + index, CloseDate = Date.today(), StageName = 'Prospecting'); - } - - static Lead getLead(Integer index) { - return new Lead(FirstName = 'Test ' + index, LastName = 'Lead ' + index, Company = 'Test Company ' + index); - } - - static Case getCase(Integer index) { - return new Case(Status = 'New', Origin = 'Web', Subject = 'Test ' + index); - } - - static Account insertAccount() { - Account account = new Account(Name = 'Test Account'); - insert account; - - return account; - } - - @SuppressWarnings('PMD.AvoidNonRestrictiveQueries') - static List getAccounts() { - return [SELECT Id, Name FROM Account]; - } - - static List insertAccounts() { - List accounts = new List{ new Account(Name = 'Test Account 1'), new Account(Name = 'Test Account 2'), new Account(Name = 'Test Account 3') }; - insert accounts; - - return accounts; - } - - static User minimumAccessUser() { - return new User( - Alias = 'newUser', - Email = 'newuser@testorg.com', - EmailEncodingKey = 'UTF-8', - LastName = 'Testing', - LanguageLocaleKey = 'en_US', - LocaleSidKey = 'en_US', - Profile = new Profile(Name = 'Minimum Access - Salesforce'), - TimeZoneSidKey = 'America/Los_Angeles', - UserName = 'btcdmllibuser@testorg.com' - ); - } -} diff --git a/package/main/default/classes/DML.cls b/package/main/default/classes/DML.cls new file mode 100644 index 0000000..34abbdd --- /dev/null +++ b/package/main/default/classes/DML.cls @@ -0,0 +1,2366 @@ +/** + * Copyright (c) 2026 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev) + * Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/dml-lib/blob/main/LICENSE) + * + * v3.1.0 + * + * PMD False Positives: + * - MethodNamingConventions: Some methods are uppercase to indicate that they are "constructors" of other internal classes + * - ApexDoc - ApexDoc is not needed, code is self-explainatory + * - CyclomaticComplexity: It is a library and we tried to put everything into ONE class + * - CognitiveComplexity: It is a library and we tried to put everything into ONE class + * - ExcessivePublicCount: It is a library and we tried to put everything into ONE class + * - FieldDeclarationsShouldBeAtStart: The most important methods and interfaces are at the top of the class + * - AvoidDebugStatements: Debug statements are used for debugging purposes + * - OperationWithLimitsInLoop: DMLs are executed by SObject OperationType and the loop is through different types + * - ApexCRUDViolation: DMLs are executed by User Mode or System Mode + * - ExcessiveClassLength: The class is a library and we tried to put everything into ONE class + * - NcssTypeCount: The class is a library and we tried to put everything into ONE class + * - PropertyNamingConventions: Some properties are PascalCase to indicate that they are "constructors" of other internal classes + * - FieldNamingConventions: Some fields have non-standard naming conventions on purpose + * - AvoidGlobalModifier - DML has package version + **/ +@SuppressWarnings( + 'PMD.MethodNamingConventions,PMD.ApexDoc,PMD.CyclomaticComplexity,PMD.CognitiveComplexity,PMD.ExcessivePublicCount,PMD.FieldDeclarationsShouldBeAtStart,PMD.AvoidDebugStatements,PMD.OperationWithLimitsInLoop,PMD.ApexCRUDViolation,PMD.ExcessiveClassLength,PMD.NcssTypeCount,PMD.PropertyNamingConventions,PMD.FieldNamingConventions,PMD.AvoidGlobalModifier' +) +global inherited sharing class DML implements Commitable { + global static Commitable Shared { + get { + if (Shared == null) { + Shared = new DML(); + } + return Shared; + } + private set; + } + + global static Record Record(SObject record) { + return new DmlRecord(record); + } + + global static Record Record(Id recordId) { + return new DmlRecord(recordId); + } + + global static Records Records(List records) { + return new DmlRecords(records); + } + + global static Records Records(Iterable recordIds) { + return new DmlRecords(recordIds); + } + + global interface Commitable { + // Insert + Commitable toInsert(SObject record); + Commitable toInsert(DML.Record record); + Commitable toInsert(List records); + Commitable toInsert(DML.Records records); + // Update + Commitable toUpdate(SObject record); + Commitable toUpdate(DML.Record record); + Commitable toUpdate(List records); + Commitable toUpdate(DML.Records records); + // Upsert + Commitable toUpsert(SObject record); + Commitable toUpsert(SObject record, SObjectField externalIdField); + Commitable toUpsert(DML.Record record); + Commitable toUpsert(List records); + Commitable toUpsert(List records, SObjectField externalIdField); + Commitable toUpsert(DML.Records records); + // Delete + Commitable toDelete(Id recordId); + Commitable toDelete(SObject record); + Commitable toDelete(Iterable recordIds); + Commitable toDelete(List records); + // Hard Delete + Commitable toHardDelete(Id recordId); + Commitable toHardDelete(SObject record); + Commitable toHardDelete(Iterable recordIds); + Commitable toHardDelete(List records); + // Undelete + Commitable toUndelete(Id recordId); + Commitable toUndelete(SObject record); + Commitable toUndelete(Iterable recordIds); + Commitable toUndelete(List records); + // Merge + Commitable toMerge(SObject mergeToRecord, SObject duplicatedRecord); + Commitable toMerge(SObject mergeToRecord, List duplicateRecords); + Commitable toMerge(SObject mergeToRecord, Id duplicatedRecordId); + Commitable toMerge(SObject mergeToRecord, Iterable duplicatedRecordIds); + // Platform Event + Commitable toPublish(SObject record); + Commitable toPublish(List records); + // Immediate Insert + OperationResult insertImmediately(SObject record); + OperationResult insertImmediately(DML.Record record); + OperationResult insertImmediately(List records); + OperationResult insertImmediately(DML.Records records); + // Immediate Update + OperationResult updateImmediately(SObject record); + OperationResult updateImmediately(DML.Record record); + OperationResult updateImmediately(List records); + OperationResult updateImmediately(DML.Records records); + // Immediate Upsert + OperationResult upsertImmediately(SObject record); + OperationResult upsertImmediately(DML.Record record); + OperationResult upsertImmediately(List records); + OperationResult upsertImmediately(DML.Records records); + // Immediate Delete + OperationResult deleteImmediately(Id recordId); + OperationResult deleteImmediately(SObject record); + OperationResult deleteImmediately(Iterable recordIds); + OperationResult deleteImmediately(List records); + // Immediate Undelete + OperationResult undeleteImmediately(Id recordId); + OperationResult undeleteImmediately(SObject record); + OperationResult undeleteImmediately(Iterable recordIds); + OperationResult undeleteImmediately(List records); + // Immediate Publish + OperationResult publishImmediately(SObject record); + OperationResult publishImmediately(List records); + // Mocking + Commitable identifier(String dmlIdentifier); // used for mocking and tracking results + // Debug + void preview(); + // Field Level Security + Commitable userMode(); + Commitable systemMode(); + // Sharing Mode + Commitable withSharing(); + Commitable withoutSharing(); + // Other configs + Commitable allowPartialSuccess(); + Commitable skipDuplicateRules(); + Commitable options(Database.DmlOptions options); + Commitable discardWork(); + Commitable commitHook(DML.Hook callback); + Commitable combineOnDuplicate(); + // Save + Result dryRun(); + Result commitWork(); + Result commitTransaction(); // make a savepoint and rollback on exception + } + + global interface Record { + Record with(SObjectField field, Object value); + Record withRelationship(SObjectField targetField, SObject relatedRecord); + Record withRelationship(SObjectField targetField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId); + + // for internal use only + EnhancedRecord get(); + SObjectType getSObjectType(); + } + + global interface Records { + Records with(SObjectField field, Object value); + Records withRelationship(SObjectField targetField, SObject relatedRecord); + Records withRelationship(SObjectField targetField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId); + + // for internal use only + List get(); + SObjectType getSObjectType(); + } + + global interface Result { + List all(); + // Per Operation + List inserts(); + List updates(); + List upserts(); + List deletes(); + List undeletes(); + List merges(); + List events(); + // Per Object OperationType + OperationResult insertsOf(SObjectType objectType); + OperationResult updatesOf(SObjectType objectType); + OperationResult upsertsOf(SObjectType objectType); + OperationResult deletesOf(SObjectType objectType); + OperationResult undeletesOf(SObjectType objectType); + OperationResult mergesOf(SObjectType objectType); + OperationResult eventsOf(SObjectType objectType); + } + + global interface OperationResult { + // Metadata + DML.OperationType operationType(); + SObjectType objectType(); + Boolean hasFailures(); + // Errors + List errors(); + // Records + List records(); + List successes(); + List failures(); + // Details + List recordResults(); + } + + global interface RecordResult { + Id id(); + SObject record(); + Boolean isSuccess(); + List errors(); + } + + global interface Error { + String message(); + System.StatusCode statusCode(); + List fields(); + } + + // Mocking + + global static DML.Mockable mock(String dmlIdentifier) { + if (!dmlIdentifierToMock.containsKey(dmlIdentifier)) { + dmlIdentifierToMock.put(dmlIdentifier, new DmlMock()); + } + return dmlIdentifierToMock.get(dmlIdentifier); + } + + global static DML.Result retrieveResultFor(String dmlIdentifier) { + if (!dmlIdentifierToResult.containsKey(dmlIdentifier)) { + throw new DmlException('No result found for dml identifier: ' + dmlIdentifier); + } + return dmlIdentifierToResult.get(dmlIdentifier); + } + + global interface Mockable { + Mockable allDmls(); + // Per Operation + Mockable allInserts(); + Mockable allUpdates(); + Mockable allUpserts(); + Mockable allDeletes(); + Mockable allUndeletes(); + Mockable allMerges(); + Mockable allPublishes(); + // Per Object OperationType + Mockable insertsFor(SObjectType objectType); + Mockable updatesFor(SObjectType objectType); + Mockable upsertsFor(SObjectType objectType); + Mockable deletesFor(SObjectType objectType); + Mockable undeletesFor(SObjectType objectType); + Mockable mergesFor(SObjectType objectType); + Mockable publishesFor(SObjectType objectType); + // Errors + Mockable exceptionOnInserts(); + Mockable exceptionOnUpdates(); + Mockable exceptionOnUpserts(); + Mockable exceptionOnDeletes(); + Mockable exceptionOnUndeletes(); + Mockable exceptionOnMerges(); + Mockable exceptionOnPublishes(); + // Per Operation Type Per Object Type + Mockable exceptionOnInsertsFor(SObjectType objectType); + Mockable exceptionOnUpdatesFor(SObjectType objectType); + Mockable exceptionOnUpsertsFor(SObjectType objectType); + Mockable exceptionOnDeletesFor(SObjectType objectType); + Mockable exceptionOnUndeletesFor(SObjectType objectType); + Mockable exceptionOnMergesFor(SObjectType objectType); + Mockable exceptionOnPublishesFor(SObjectType objectType); + } + + // Hooks + + global interface Hook { + void before(); + void after(Result result); + } + + // Implementation + + global enum OperationType { + INSERT_DML, + UPSERT_DML, + UPDATE_DML, + MERGE_DML, + DELETE_DML, + UNDELETE_DML, + PUBLISH_DML + } + + private Configuration configuration; + private StrategiesStorage strategiesStorage; + private DependencyOrchestrator dependencyOrchestrator; + private LinearOrchestrator linearOrchestrator; + + private Hook hook; + + @TestVisible + private static final RandomIdGenerator randomIdGenerator = new RandomIdGenerator(); + private static final Map dmlIdentifierToResult = new Map(); + private static final Map dmlIdentifierToMock = new Map(); + + global DML() { + this.configuration = new Configuration(); + this.strategiesStorage = new StrategiesStorage(); + this.dependencyOrchestrator = new DependencyOrchestrator(); + this.linearOrchestrator = new LinearOrchestrator(this.configuration); + } + + // Insert + + global Commitable toInsert(SObject record) { + return this.toInsert(Record(record)); + } + + global Commitable toInsert(Record record) { + return this.registerInDependencyOrchestrator(this.getInsertStrategy(record.getSObjectType()), record); + } + + global Commitable toInsert(List records) { + return this.toInsert(Records(records)); + } + + global Commitable toInsert(Records records) { + return this.registerInDependencyOrchestrator(this.getInsertStrategy(records.getSObjectType()), records); + } + + global OperationResult insertImmediately(SObject record) { + return this.insertImmediately(Record(record)); + } + + global OperationResult insertImmediately(Record record) { + return this.executeImmediately(this.getInsertStrategy(record.getSObjectType()), record); + } + + global OperationResult insertImmediately(List records) { + return this.insertImmediately(Records(records)); + } + + global OperationResult insertImmediately(Records records) { + return this.executeImmediately(this.getInsertStrategy(records.getSObjectType()), records); + } + + private InsertStrategy getInsertStrategy(SObjectType objectType) { + return new InsertStrategy(objectType, this.configuration); + } + + // Update + + global Commitable toUpdate(SObject record) { + return this.toUpdate(Record(record)); + } + + global Commitable toUpdate(Record record) { + return this.registerInLinearOrchestrator(this.getUpdateStrategy(record.getSObjectType()), record); + } + + global Commitable toUpdate(List records) { + return this.toUpdate(Records(records)); + } + + global Commitable toUpdate(Records records) { + return this.registerInLinearOrchestrator(this.getUpdateStrategy(records.getSObjectType()), records); + } + + global OperationResult updateImmediately(SObject record) { + return this.updateImmediately(Record(record)); + } + + global OperationResult updateImmediately(Record record) { + return this.executeImmediately(this.getUpdateStrategy(record.getSObjectType()), record); + } + + global OperationResult updateImmediately(List records) { + return this.updateImmediately(Records(records)); + } + + global OperationResult updateImmediately(Records records) { + return this.executeImmediately(this.getUpdateStrategy(records.getSObjectType()), records); + } + + private UpdateStrategy getUpdateStrategy(SObjectType objectType) { + return new UpdateStrategy(objectType, this.configuration); + } + + // Upsert + + global Commitable toUpsert(SObject record) { + return this.toUpsert(Record(record)); + } + + global Commitable toUpsert(SObject record, SObjectField externalIdField) { + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(record.getSObjectType()).withExternalIdField(externalIdField), Record(record)); + } + + global Commitable toUpsert(Record record) { + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(record.getSObjectType()), record); + } + + global Commitable toUpsert(List records) { + return this.toUpsert(Records(records)); + } + + global Commitable toUpsert(List records, SObjectField externalIdField) { + Records dmlRecords = Records(records); + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(dmlRecords.getSObjectType()).withExternalIdField(externalIdField), dmlRecords); + } + + global Commitable toUpsert(Records records) { + return this.registerInDependencyOrchestrator(this.getUpsertStrategy(records.getSObjectType()), records); + } + + global OperationResult upsertImmediately(SObject record) { + return this.upsertImmediately(Record(record)); + } + + global OperationResult upsertImmediately(Record record) { + return this.executeImmediately(this.getUpsertStrategy(record.getSObjectType()), record); + } + + global OperationResult upsertImmediately(List records) { + return this.upsertImmediately(Records(records)); + } + + global OperationResult upsertImmediately(Records records) { + return this.executeImmediately(this.getUpsertStrategy(records.getSObjectType()), records); + } + + private UpsertStrategy getUpsertStrategy(SObjectType objectType) { + return new UpsertStrategy(objectType, this.configuration); + } + + // Delete + + global Commitable toDelete(Id recordId) { + return this.toDelete(Record(recordId)); + } + + global Commitable toDelete(SObject record) { + return this.toDelete(Record(record)); + } + + private Commitable toDelete(Record record) { + return this.registerInLinearOrchestrator(this.getDeleteStrategy(record.getSObjectType()), record); + } + + global Commitable toDelete(Iterable recordIds) { + return this.toDelete(Records(recordIds)); + } + + global Commitable toDelete(List records) { + return this.toDelete(Records(records)); + } + + private Commitable toDelete(Records records) { + return this.registerInLinearOrchestrator(this.getDeleteStrategy(records.getSObjectType()), records); + } + + global OperationResult deleteImmediately(Id recordId) { + return this.deleteImmediately(Record(recordId)); + } + + global OperationResult deleteImmediately(SObject record) { + return this.deleteImmediately(Record(record)); + } + + private OperationResult deleteImmediately(Record record) { + return this.executeImmediately(this.getDeleteStrategy(record.getSObjectType()), record); + } + + global OperationResult deleteImmediately(Iterable recordIds) { + return this.deleteImmediately(Records(recordIds)); + } + + global OperationResult deleteImmediately(List records) { + return this.deleteImmediately(Records(records)); + } + + private OperationResult deleteImmediately(Records records) { + return this.executeImmediately(this.getDeleteStrategy(records.getSObjectType()), records); + } + + private DeleteStrategy getDeleteStrategy(SObjectType objectType) { + return new DeleteStrategy(objectType, this.configuration); + } + + // Hard Delete + + global Commitable toHardDelete(Id recordId) { + Record dmlRecord = Record(recordId); + return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecord.getSObjectType()).withHardDelete(), dmlRecord); + } + + global Commitable toHardDelete(SObject record) { + Record dmlRecord = Record(record); + return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecord.getSObjectType()).withHardDelete(), dmlRecord); + } + + global Commitable toHardDelete(Iterable recordIds) { + Records dmlRecords = Records(recordIds); + return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecords.getSObjectType()).withHardDelete(), dmlRecords); + } + + global Commitable toHardDelete(List records) { + Records dmlRecords = Records(records); + return this.registerInLinearOrchestrator(this.getDeleteStrategy(dmlRecords.getSObjectType()).withHardDelete(), dmlRecords); + } + + // Undelete + + global Commitable toUndelete(Id recordId) { + return this.toUndelete(Record(recordId)); + } + + global Commitable toUndelete(SObject record) { + return this.toUndelete(Record(record)); + } + + private Commitable toUndelete(Record record) { + return this.registerInLinearOrchestrator(this.getUndeleteStrategy(record.getSObjectType()), record); + } + + global Commitable toUndelete(Iterable recordIds) { + return this.toUndelete(Records(recordIds)); + } + + global Commitable toUndelete(List records) { + return this.toUndelete(Records(records)); + } + + private Commitable toUndelete(Records records) { + return this.registerInLinearOrchestrator(this.getUndeleteStrategy(records.getSObjectType()), records); + } + + global OperationResult undeleteImmediately(Id recordId) { + return this.undeleteImmediately(Record(recordId)); + } + + global OperationResult undeleteImmediately(SObject record) { + return this.undeleteImmediately(Record(record)); + } + + private OperationResult undeleteImmediately(Record record) { + return this.executeImmediately(this.getUndeleteStrategy(record.getSObjectType()), record); + } + + global OperationResult undeleteImmediately(Iterable recordIds) { + return this.undeleteImmediately(Records(recordIds)); + } + + global OperationResult undeleteImmediately(List records) { + return this.undeleteImmediately(Records(records)); + } + + private OperationResult undeleteImmediately(Records records) { + return this.executeImmediately(this.getUndeleteStrategy(records.getSObjectType()), records); + } + + private UndeleteStrategy getUndeleteStrategy(SObjectType objectType) { + return new UndeleteStrategy(objectType, this.configuration); + } + + // Merge + + global Commitable toMerge(SObject mergeToRecord, SObject duplicatedRecord) { + Record dmlMergeToRecord = Record(mergeToRecord); + return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Record(duplicatedRecord)); + } + + global Commitable toMerge(SObject mergeToRecord, Id duplicatedRecordId) { + Record dmlMergeToRecord = Record(mergeToRecord); + return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Record(duplicatedRecordId)); + } + + private Commitable toMerge(MergeStrategy mergeStrategy, Record duplicatedRecord) { + return this.registerInLinearOrchestrator(mergeStrategy, duplicatedRecord); + } + + global Commitable toMerge(SObject mergeToRecord, List duplicateRecords) { + Record dmlMergeToRecord = Record(mergeToRecord); + return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Records(duplicateRecords)); + } + + global Commitable toMerge(SObject mergeToRecord, Iterable duplicatedRecordIds) { + Record dmlMergeToRecord = Record(mergeToRecord); + return this.toMerge(this.getMergeStrategy(dmlMergeToRecord.getSObjectType(), dmlMergeToRecord), Records(duplicatedRecordIds)); + } + + private Commitable toMerge(MergeStrategy mergeStrategy, Records duplicateRecords) { + return this.registerInLinearOrchestrator(mergeStrategy, duplicateRecords); + } + + private MergeStrategy getMergeStrategy(SObjectType objectType, Record mergeToRecord) { + return new MergeStrategy(objectType, this.configuration, mergeToRecord); + } + + // Platform Event + + global Commitable toPublish(SObject record) { + Record dmlRecord = Record(record); + return this.registerInLinearOrchestrator(this.getPlatformEventStrategy(dmlRecord.getSObjectType()), dmlRecord); + } + + global Commitable toPublish(List records) { + Records dmlRecords = Records(records); + return this.registerInLinearOrchestrator(this.getPlatformEventStrategy(dmlRecords.getSObjectType()), dmlRecords); + } + + global OperationResult publishImmediately(SObject record) { + Record dmlRecord = Record(record); + return this.executeImmediately(this.getPlatformEventStrategy(dmlRecord.getSObjectType()), dmlRecord); + } + + global OperationResult publishImmediately(List records) { + Records dmlRecords = Records(records); + return this.executeImmediately(this.getPlatformEventStrategy(dmlRecords.getSObjectType()), dmlRecords); + } + + private PlatformEventStrategy getPlatformEventStrategy(SObjectType objectType) { + return new PlatformEventStrategy(objectType, this.configuration); + } + + // Helpers + + private OperationResult executeImmediately(DmlStrategy strategy, Record record) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + OperationResult result = existing.execute(new List{ record.get() }); + this.storeResult(result); + return result; + } + + private OperationResult executeImmediately(DmlStrategy strategy, Records records) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + OperationResult result = existing.execute(records); + this.storeResult(result); + return result; + } + + private Commitable registerInDependencyOrchestrator(DmlStrategy strategy, Record record) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + this.dependencyOrchestrator.register(existing, record); + return this; + } + + private Commitable registerInDependencyOrchestrator(DmlStrategy strategy, Records records) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + this.dependencyOrchestrator.register(existing, records); + return this; + } + + private Commitable registerInLinearOrchestrator(DmlStrategy strategy, Record record) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + this.linearOrchestrator.register(existing, record); + return this; + } + + private Commitable registerInLinearOrchestrator(DmlStrategy strategy, Records records) { + DmlStrategy existing = this.strategiesStorage.intern(strategy); + this.linearOrchestrator.register(existing, records); + return this; + } + + // Identifier + + global Commitable identifier(String dmlIdentifier) { + this.configuration.identifier(dmlIdentifier); + return this; + } + + // Debug + + global void preview() { + this.configuration.preview(); + } + + // Field Level Security + + global Commitable userMode() { + return this.setAccessMode(System.AccessLevel.USER_MODE); + } + + global Commitable systemMode() { + return this.setAccessMode(System.AccessLevel.SYSTEM_MODE); + } + + private Commitable setAccessMode(System.AccessLevel accessMode) { + this.configuration.accessMode(accessMode); + return this; + } + + // Sharing Mode + + global Commitable withSharing() { + this.configuration.withSharing(); + return this; + } + + global Commitable withoutSharing() { + this.configuration.withoutSharing(); + return this; + } + + // Other configs + + global Commitable allowPartialSuccess() { + this.configuration.allowPartialSuccess(); + return this; + } + + global Commitable skipDuplicateRules() { + this.configuration.skipDuplicateRules(); + return this; + } + + global Commitable combineOnDuplicate() { + this.configuration.combineOnDuplicate(); + return this; + } + + global Commitable options(Database.DmlOptions options) { + this.configuration.options(options); + return this; + } + + global Commitable discardWork() { + this.reset(); + return this; + } + + // Hooks + + global Commitable commitHook(Hook callback) { + this.hook = callback; + return this; + } + + global Result dryRun() { + Savepoint savePoint = Database.setSavepoint(); + + try { + DmlResult result = new DmlResult(); + + this.hook?.before(); + + this.dependencyOrchestrator.execute(result); + this.linearOrchestrator.execute(result); + + this.hook?.after(result); + + return result; + } finally { + Database.rollback(savePoint); + Database.releaseSavepoint(savePoint); + } + } + + global Result commitWork() { + DmlResult result = new DmlResult(); + + try { + this.hook?.before(); + + this.dependencyOrchestrator.execute(result); + this.linearOrchestrator.execute(result); + + this.storeResult(result); + + this.hook?.after(result); + } finally { + this.reset(); + } + + return result; + } + + private void storeResult(DmlResult result) { + if (String.isBlank(this.configuration.dmlIdentifier)) { + return; + } + + dmlIdentifierToResult.put(this.configuration.dmlIdentifier, result); + } + + private void storeResult(OperationResult operationResult) { + if (String.isBlank(this.configuration.dmlIdentifier)) { + return; + } + + DmlResult result = (DmlResult) dmlIdentifierToResult.get(this.configuration.dmlIdentifier); + + if (result == null) { + result = new DmlResult(); + } + + result.add(operationResult); + + dmlIdentifierToResult.put(this.configuration.dmlIdentifier, result); + } + + global Result commitTransaction() { + if (!this.configuration.allOrNone) { + throw new DmlException('commitTransaction() is not supported when allOrNone=false'); + } + + Savepoint savePoint = Database.setSavepoint(); + + try { + return this.commitWork(); + } catch (Exception e) { + Database.rollback(savePoint); + throw e; + } finally { + Database.releaseSavepoint(savePoint); + } + } + + private void reset() { + this.dependencyOrchestrator = new DependencyOrchestrator(); + this.linearOrchestrator = new LinearOrchestrator(this.configuration); + } + + private class StrategiesStorage { + private Map strategyByOperationId = new Map(); + + private DmlStrategy intern(DmlStrategy candidate) { + String operationId = candidate.operationId(); + + DmlStrategy existing = this.strategyByOperationId.get(operationId); + + if (existing == null) { + this.strategyByOperationId.put(operationId, candidate); + return candidate; + } + + return existing; + } + } + + private class DependencyOrchestrator { + private Map enhancedRecordsById = new Map(); + private List enhancedRecords = new List(); + + private OrderDependencyGraph orderDependencyGraph = new OrderDependencyGraph(); + + private void register(DmlStrategy strategy, Records records) { + for (Record record : records.get()) { + this.register(strategy, record); + } + } + + private void register(DmlStrategy strategy, Record record) { + EnhancedRecord enhancedRecord = record.get(); + + strategy.validate(enhancedRecord); + enhancedRecord.setDatabaseStrategy(strategy); + + this.enhancedRecords.add(enhancedRecord); + } + + private void execute(DmlResult result) { + this.addTemporaryIdsToBuildDependencyGraph(); + + List> executionWaves = this.orderDependencyGraph.computeExecutionWaves(); + + for (List waveRecordIds : executionWaves) { + for (ProcessingGroup processingGroup : this.groupRecordsByStrategy(waveRecordIds)) { + result.add(processingGroup.strategy.commitWork(processingGroup.records)); + } + } + } + + private void addTemporaryIdsToBuildDependencyGraph() { + for (EnhancedRecord enhancedRecord : this.enhancedRecords) { + enhancedRecord.setTemporaryId(); + enhancedRecord.addToDependencyGraph(this.orderDependencyGraph); + + this.enhancedRecordsById.put(enhancedRecord.getRecordId(), enhancedRecord); + } + } + + private List groupRecordsByStrategy(List enhancedRecordIds) { + Map processingGroupsByOperationId = new Map(); + + for (Id enhancedRecordId : enhancedRecordIds) { + EnhancedRecord enhancedRecord = this.enhancedRecordsById.get(enhancedRecordId); + + if (enhancedRecord == null) { + // record was commited without unit of work, or executeImmediately was used + // so it was not registered in the dependency graph + continue; + } + + enhancedRecord.clearTemporaryId(); + + String operationId = enhancedRecord.strategy.operationId(); + + if (!processingGroupsByOperationId.containsKey(operationId)) { + processingGroupsByOperationId.put(operationId, new ProcessingGroup(enhancedRecord.strategy)); + } + + processingGroupsByOperationId.get(operationId).add(enhancedRecord); + } + + return processingGroupsByOperationId.values(); + } + } + + private class LinearOrchestrator { + private Map processingGroupByOperationId = new Map(); + private Map> processingGroupsByOperationType = new Map>{ + OperationType.UPDATE_DML => new List(), + OperationType.MERGE_DML => new List(), + OperationType.DELETE_DML => new List(), + OperationType.UNDELETE_DML => new List(), + OperationType.PUBLISH_DML => new List() + }; + + private Configuration configuration; + + private LinearOrchestrator(Configuration configuration) { + this.configuration = configuration; + } + + private void register(DmlStrategy strategy, Records records) { + for (Record record : records.get()) { + this.register(strategy, record); + } + } + + private void register(DmlStrategy strategy, Record record) { + EnhancedRecord enhancedRecord = record.get(); + + strategy.validate(enhancedRecord); + + this.assignRecordToProcessingGroup(strategy, enhancedRecord); + } + + private void assignRecordToProcessingGroup(DmlStrategy strategy, EnhancedRecord enhancedRecord) { + ProcessingGroup processingGroup = this.processingGroupByOperationId.get(strategy.operationId()); + + if (processingGroup == null) { + processingGroup = new ProcessingGroup(strategy, this.configuration.duplicateCombineStrategy); + + this.processingGroupByOperationId.put(strategy.operationId(), processingGroup); + this.processingGroupsByOperationType.get(strategy.getOperationType()).add(processingGroup); + } + + if (processingGroup.hasDuplicate(enhancedRecord)) { + processingGroup.executeDuplicateStrategy(enhancedRecord); + return; + } + + processingGroup.add(enhancedRecord); + } + + private void execute(DmlResult result) { + for (OperationType operationType : this.processingGroupsByOperationType.keySet()) { + for (ProcessingGroup processingGroup : this.processingGroupsByOperationType.get(operationType)) { + result.add(processingGroup.strategy.commitWork(processingGroup.records)); + } + } + } + } + + private class ProcessingGroup { + private DmlStrategy strategy; + private DuplicateCombineStrategy duplicateCombineStrategy; + + private Map recordsById = new Map(); + private List records = new List(); + + private ProcessingGroup(DmlStrategy strategy) { + this.strategy = strategy; + } + + private ProcessingGroup(DmlStrategy strategy, DuplicateCombineStrategy duplicateCombineStrategy) { + this(strategy); + this.duplicateCombineStrategy = duplicateCombineStrategy; + } + + private Boolean hasDuplicate(EnhancedRecord newRecord) { + return newRecord.hasId() && this.recordsById.containsKey(newRecord.getRecordId()); + } + + private void executeDuplicateStrategy(EnhancedRecord newRecord) { + EnhancedRecord existingRecord = this.recordsById.get(newRecord.getRecordId()); + + this.duplicateCombineStrategy.combine(existingRecord, newRecord); + } + + private void add(EnhancedRecord newRecord) { + this.records.add(newRecord); + + if (newRecord.hasId()) { + this.recordsById.put(newRecord.getRecordId(), newRecord); + } + } + } + + private class DmlResult implements Result { + private Map> operationResultsByObjectType = new Map>(); + + private DmlResult() { + for (OperationType operationType : OperationType.values()) { + this.operationResultsByObjectType.put(operationType, new Map()); + } + } + + private DmlResult add(OperationResult result) { + OperationType operationType = result.operationType(); + SObjectType objectType = result.objectType(); + + SObjectAggregatedResult aggregatedResult = this.operationResultsByObjectType.get(operationType).get(objectType); + + if (aggregatedResult == null) { + aggregatedResult = new SObjectAggregatedResult(operationType, objectType); + this.operationResultsByObjectType.get(operationType).put(objectType, aggregatedResult); + } + + aggregatedResult.add(result); + + return this; + } + + public List inserts() { + return this.getOperationResults(OperationType.INSERT_DML); + } + + public List updates() { + return this.getOperationResults(OperationType.UPDATE_DML); + } + + public List upserts() { + return this.getOperationResults(OperationType.UPSERT_DML); + } + + public List deletes() { + return this.getOperationResults(OperationType.DELETE_DML); + } + + public List undeletes() { + return this.getOperationResults(OperationType.UNDELETE_DML); + } + + public List merges() { + return this.getOperationResults(OperationType.MERGE_DML); + } + + public List events() { + return this.getOperationResults(OperationType.PUBLISH_DML); + } + + public OperationResult insertsOf(SObjectType objectType) { + return this.getOperationResult(OperationType.INSERT_DML, objectType); + } + + public OperationResult updatesOf(SObjectType objectType) { + return this.getOperationResult(OperationType.UPDATE_DML, objectType); + } + + public OperationResult upsertsOf(SObjectType objectType) { + return this.getOperationResult(OperationType.UPSERT_DML, objectType); + } + + public OperationResult deletesOf(SObjectType objectType) { + return this.getOperationResult(OperationType.DELETE_DML, objectType); + } + + public OperationResult undeletesOf(SObjectType objectType) { + return this.getOperationResult(OperationType.UNDELETE_DML, objectType); + } + + public OperationResult mergesOf(SObjectType objectType) { + return this.getOperationResult(OperationType.MERGE_DML, objectType); + } + + public OperationResult eventsOf(SObjectType objectType) { + return this.getOperationResult(OperationType.PUBLISH_DML, objectType); + } + + private List getOperationResults(OperationType operationType) { + return this.operationResultsByObjectType.get(operationType).values(); + } + + private OperationResult getOperationResult(OperationType operationType, SObjectType objectType) { + return this.operationResultsByObjectType.get(operationType).get(objectType) ?? new SObjectAggregatedResult(operationType, objectType); + } + + public List all() { + List allResults = new List(); + + for (OperationType operationType : this.operationResultsByObjectType.keySet()) { + allResults.addAll(this.operationResultsByObjectType.get(operationType).values()); + } + + return allResults; + } + } + + private class Configuration { + private System.AccessLevel accessMode = System.AccessLevel.USER_MODE; + private Database.DmlOptions options = new Database.DmlOptions(); + private DmlSharing sharingExecutor = new InheritedSharing(); + private DuplicateCombineStrategy duplicateCombineStrategy = new ThrownExceptionDuplicateStrategy(); + private Boolean allOrNone = true; + private String dmlIdentifier = null; + + private Configuration() { + this.options.optAllOrNone = true; + } + + private void accessMode(System.AccessLevel accessMode) { + this.accessMode = accessMode; + } + + private void withSharing() { + this.sharingExecutor = new WithSharing(); + } + + private void withoutSharing() { + this.sharingExecutor = new WithoutSharing(); + } + + private void identifier(String dmlIdentifier) { + this.dmlIdentifier = dmlIdentifier; + } + + private void allowPartialSuccess() { + this.allOrNone = false; + this.options.optAllOrNone = false; + } + + private void combineOnDuplicate() { + this.duplicateCombineStrategy = new MergeDuplicateStrategy(); + } + + private void options(Database.DmlOptions options) { + this.options = options; + this.options.optAllOrNone = this.options.optAllOrNone ?? this.allOrNone ?? true; + } + + private void preview() { + System.debug( + LoggingLevel.ERROR, + '\n\n============ DML Configuration ============' + + '\nOptions: ' + + JSON.serializePretty(this.options) + + '\nSharing Executor: ' + + String.valueOf(this.sharingExecutor).split(':')[0] + + '\nAll Or None: ' + + this.allOrNone + + '\nDML Identifier: ' + + this.dmlIdentifier + + '\n=======================================\n' + ); + } + + private void skipDuplicateRules() { + this.options.duplicateRuleHeader.allowSave = true; + } + } + + private abstract class DmlStrategy { + private SObjectType objectType; + private Configuration globalConfiguration; + + protected DmlStrategy(SObjectType objectType, Configuration configuration) { + this.objectType = objectType; + this.globalConfiguration = configuration; + } + + private SObjectType getObjectType() { + return this.objectType; + } + + private OperationResult commitWork(List records) { + return this.globalConfiguration.sharingExecutor.execute(this, records); + } + + private OperationResult execute(Records records) { + List enhancedRecords = new List(); + + for (Record record : records.get()) { + enhancedRecords.add(record.get()); + } + + return this.execute(enhancedRecords); + } + + private OperationResult execute(List records) { + List recordsToProcess = this.getRecordsToProcess(records); + + ExecutionResult result = new ExecutionResult(this.getOperationType(), this.objectType).setRecords(recordsToProcess); + + DmlMock dmlMock = dmlIdentifierToMock.get(this.globalConfiguration.dmlIdentifier); + + if (dmlMock != null && dmlMock.shouldBeMocked(this.getOperationType(), this.objectType)) { + return result.setRecordResults(dmlMock.getMockedRecordResults(this, recordsToProcess)); + } + + return result.setRecordResults(this.getResultAdapter().get(this.executeDml(recordsToProcess), recordsToProcess)); + } + + private List getRecordsToProcess(List records) { + List recordsToProcess = new List(); + + for (EnhancedRecord enhancedRecord : records) { + enhancedRecord.resolveRecordRelationships(); + recordsToProcess.add(enhancedRecord.getRecord()); + } + + return recordsToProcess; + } + + protected abstract OperationType getOperationType(); + protected abstract DmlResultAdapter getResultAdapter(); + protected abstract List executeDml(List recordsToProcess); + protected abstract RecordSummary executeMockedDml(SObject record); + + protected virtual void validate(EnhancedRecord enhancedRecord) { + return; + } + + protected virtual String operationId() { + return this.getOperationType().name() + '_' + (this.getObjectType()?.toString() ?? 'EMPTY'); + } + } + + // Strategies + + private inherited sharing class InsertStrategy extends DmlStrategy { + private InsertStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + protected override OperationType getOperationType() { + return OperationType.INSERT_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new SaveResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (enhancedRecord.hasId()) { + throw new DmlException('Only new records can be registered as new.'); + } + } + + protected override List executeDml(List recordsToProcess) { + return Database.insert(recordsToProcess, this.globalConfiguration.options, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + record.put('Id', randomIdGenerator.get(record.getSObjectType())); + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class UpsertStrategy extends DmlStrategy { + private SObjectField externalIdField; + + private UpsertStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + private UpsertStrategy withExternalIdField(SObjectField externalIdField) { + this.externalIdField = externalIdField; + return this; + } + + protected override String operationId() { + String externalId = this.externalIdField == null ? 'Id' : this.externalIdField.toString(); + return super.operationId() + '_' + externalId; + } + + protected override OperationType getOperationType() { + return OperationType.UPSERT_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new UpsertResultAdapter(); + } + + protected override List executeDml(List recordsToProcess) { + if (this.externalIdField == null) { + return Database.upsert(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + } + + return Database.upsert(recordsToProcess, this.externalIdField, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + if (record.Id == null) { + record.put('Id', randomIdGenerator.get(record.getSObjectType())); + } + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class UpdateStrategy extends DmlStrategy { + private UpdateStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + protected override OperationType getOperationType() { + return OperationType.UPDATE_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new SaveResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (!enhancedRecord.hasId()) { + throw new DmlException('Only existing records can be updated.'); + } + } + + protected override List executeDml(List recordsToProcess) { + return Database.update(recordsToProcess, this.globalConfiguration.options, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class MergeStrategy extends DmlStrategy { + private EnhancedRecord mergeToRecord; + + private MergeStrategy(SObjectType objectType, Configuration configuration, Record mergeToRecord) { + super(objectType, configuration); + this.validate(mergeToRecord.get()); + this.mergeToRecord = mergeToRecord.get(); + } + + protected override String operationId() { + return super.operationId() + '_' + this.mergeToRecord.getRecordId(); + } + + protected override OperationType getOperationType() { + return OperationType.MERGE_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new MergeResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (!enhancedRecord.hasId()) { + throw new DmlException('Only existing records can be merged.'); + } + } + + protected override List executeDml(List recordsToProcess) { + return Database.merge(this.mergeToRecord.getRecord(), recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class DeleteStrategy extends DmlStrategy { + private Boolean makeHardDelete = false; + + private DeleteStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + private DeleteStrategy withHardDelete() { + this.makeHardDelete = true; + return this; + } + + protected override String operationId() { + return super.operationId() + '_' + this.makeHardDelete; + } + + protected override OperationType getOperationType() { + return OperationType.DELETE_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new DeleteResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (!enhancedRecord.hasId()) { + throw new DmlException('Only existing records can be registered as deleted.'); + } + } + + protected override List executeDml(List recordsToProcess) { + List dmlResults = Database.delete(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + + if (this.makeHardDelete) { + Database.emptyRecycleBin(recordsToProcess); + } + + return dmlResults; + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class UndeleteStrategy extends DmlStrategy { + private UndeleteStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + protected override OperationType getOperationType() { + return OperationType.UNDELETE_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new UndeleteResultAdapter(); + } + + protected override void validate(EnhancedRecord enhancedRecord) { + if (!enhancedRecord.hasId()) { + throw new DmlException('Only deleted records can be undeleted.'); + } + } + + protected override List executeDml(List recordsToProcess) { + return Database.undelete(recordsToProcess, this.globalConfiguration.allOrNone, this.globalConfiguration.accessMode); + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(record.Id).record(record); + } + } + + private inherited sharing class PlatformEventStrategy extends DmlStrategy { + private PlatformEventStrategy(SObjectType objectType, Configuration configuration) { + super(objectType, configuration); + } + + protected override OperationType getOperationType() { + return OperationType.PUBLISH_DML; + } + + protected override DmlResultAdapter getResultAdapter() { + return new SaveResultAdapter(); + } + + protected override List executeDml(List recordsToProcess) { + return EventBus.publish(recordsToProcess); + } + + protected override RecordSummary executeMockedDml(SObject record) { + return new RecordSummary().isSuccess(true).recordId(randomIdGenerator.get(record.getSObjectType())).record(record); + } + } + + // Sharing Executors + + private interface DmlSharing { + OperationResult execute(DmlStrategy strategy, List records); + } + + private inherited sharing class InheritedSharing implements DmlSharing { + public OperationResult execute(DmlStrategy strategy, List records) { + return strategy.execute(records); + } + } + + private without sharing class WithoutSharing implements DmlSharing { + public OperationResult execute(DmlStrategy strategy, List records) { + return strategy.execute(records); + } + } + + private with sharing class WithSharing implements DmlSharing { + public OperationResult execute(DmlStrategy strategy, List records) { + return strategy.execute(records); + } + } + + // Result Adapters + + private abstract class DmlResultAdapter { + public List get(List dmlResults, List processedRecords) { + List recordResults = new List(); + + for (Integer i = 0; i < dmlResults.size(); i++) { + recordResults.add(this.transform(dmlResults[i]).record(processedRecords[i])); + } + + return recordResults; + } + + protected abstract RecordSummary transform(Object result); + } + + private class SaveResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.SaveResult saveResult = (Database.SaveResult) result; + + return new RecordSummary().isSuccess(saveResult.isSuccess()).recordId(saveResult.getId()).errors(saveResult.getErrors()); + } + } + + private class UpsertResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.UpsertResult upsertResult = (Database.UpsertResult) result; + + return new RecordSummary().isSuccess(upsertResult.isSuccess()).recordId(upsertResult.getId()).errors(upsertResult.getErrors()); + } + } + + private class MergeResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.MergeResult mergeResult = (Database.MergeResult) result; + + return new RecordSummary().isSuccess(mergeResult.isSuccess()).recordId(mergeResult.getId()).errors(mergeResult.getErrors()); + } + } + + private class DeleteResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.DeleteResult deleteResult = (Database.DeleteResult) result; + + return new RecordSummary().isSuccess(deleteResult.isSuccess()).recordId(deleteResult.getId()).errors(deleteResult.getErrors()); + } + } + + private class UndeleteResultAdapter extends DmlResultAdapter { + public override RecordSummary transform(Object result) { + Database.UndeleteResult undeleteResult = (Database.UndeleteResult) result; + + return new RecordSummary().isSuccess(undeleteResult.isSuccess()).recordId(undeleteResult.getId()).errors(undeleteResult.getErrors()); + } + } + + private interface DuplicateCombineStrategy { + void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord); + } + + private class ThrownExceptionDuplicateStrategy implements DuplicateCombineStrategy { + public void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord) { + throw new DmlException('Duplicate records found during registration. Fix the code or use the combineOnDuplicate() method.'); + } + } + + private class MergeDuplicateStrategy implements DuplicateCombineStrategy { + public void combine(EnhancedRecord mergeToRecord, EnhancedRecord duplicateRecord) { + SObject existingRecord = mergeToRecord.getRecord(); + SObject newRecord = duplicateRecord.getRecord(); + + for (String field : newRecord.getPopulatedFieldsAsMap().keySet()) { + existingRecord.put(field, newRecord.get(field)); + } + } + } + + private abstract class DmlOperationResult implements OperationResult { + private OperationType type; + private SObjectType objectType; + private List recordResults = new List(); + private List records = new List(); + + private Boolean isResultsCached = false; + private List cachedSuccesses = new List(); + private List cachedFailures = new List(); + private List cachedErrors = new List(); + + private DmlOperationResult(OperationType type, SObjectType objectType) { + this.type = type; + this.objectType = objectType; + } + + public SObjectType objectType() { + return this.objectType; + } + + public OperationType operationType() { + return this.type; + } + + public Boolean hasFailures() { + return !this.failures().isEmpty(); + } + + public List errors() { + this.cacheResults(); + return this.cachedErrors; + } + + public List records() { + return this.records; + } + + public List successes() { + this.cacheResults(); + return this.cachedSuccesses; + } + + public List failures() { + this.cacheResults(); + return this.cachedFailures; + } + + public List recordResults() { + return this.recordResults; + } + + private void cacheResults() { + if (this.isResultsCached) { + return; + } + + for (RecordResult recordResult : this.recordResults) { + if (recordResult.isSuccess()) { + this.cachedSuccesses.add(recordResult.record()); + continue; + } + + this.cachedErrors.addAll(recordResult.errors()); + this.cachedFailures.add(recordResult.record()); + } + + this.isResultsCached = true; + } + } + + private class ExecutionResult extends DmlOperationResult { + private ExecutionResult(OperationType type, SObjectType objectType) { + super(type, objectType); + } + + private ExecutionResult setRecords(List records) { + this.records = records; + return this; + } + + private ExecutionResult setRecordResults(List recordResults) { + this.recordResults = recordResults; + return this; + } + } + + private class SObjectAggregatedResult extends DmlOperationResult { + private SObjectAggregatedResult(OperationType type, SObjectType objectType) { + super(type, objectType); + } + + private SObjectAggregatedResult add(OperationResult recordResult) { + this.recordResults.addAll(recordResult.recordResults()); + this.records.addAll(recordResult.records()); + return this; + } + } + + private class RecordSummary implements RecordResult { + private Id recordId; + private SObject record; + private Boolean isSuccess = false; + private List errors = new List(); + + public Id id() { + return this.recordId; + } + + public SObject record() { + return this.record; + } + + public Boolean isSuccess() { + return this.isSuccess; + } + + public List errors() { + return this.errors; + } + + private RecordSummary recordId(Id recordId) { + this.recordId = recordId; + return this; + } + + private RecordSummary record(SObject record) { + this.record = record; + return this; + } + + @SuppressWarnings('PMD.AvoidBooleanMethodParameters') + private RecordSummary isSuccess(Boolean isSuccess) { + this.isSuccess = isSuccess; + return this; + } + + private RecordSummary error(Error error) { + this.errors.add(error); + return this; + } + + private RecordSummary errors(List errors) { + for (Database.Error error : errors ?? new List()) { + this.errors.add(new RecordProcessingError(error)); + } + return this; + } + } + + private class RecordProcessingError implements Error { + private String message; + private System.StatusCode statusCode; + private List fields; + + private RecordProcessingError() { + // Default constructor for builder pattern + } + + private RecordProcessingError(Database.Error error) { + this.message = error.getMessage(); + this.statusCode = error.getStatusCode(); + this.fields = error.getFields(); + } + + private RecordProcessingError setMessage(String message) { + this.message = message; + return this; + } + + private RecordProcessingError setStatusCode(System.StatusCode statusCode) { + this.statusCode = statusCode; + return this; + } + + private RecordProcessingError setFields(List fields) { + this.fields = fields; + return this; + } + + public String message() { + return this.message; + } + + public System.StatusCode statusCode() { + return this.statusCode; + } + + public List fields() { + return this.fields; + } + } + + private class DmlMock implements Mockable { + private Set mockedDmlTypes = new Set(); + private Set thrownExceptionDmlTypes = new Set(); + + private Map> mockedObjectTypesByDmlType = new Map>(); + private Map> thrownExceptionDmlTypesByObjectTypes = new Map>(); + + public Mockable allDmls() { + this.mockedDmlTypes.addAll(OperationType.values()); + return this; + } + + public Mockable allInserts() { + return this.thenMockDml(OperationType.INSERT_DML); + } + + public Mockable allUpdates() { + return this.thenMockDml(OperationType.UPDATE_DML); + } + + public Mockable allUpserts() { + return this.thenMockDml(OperationType.UPSERT_DML); + } + + public Mockable allDeletes() { + return this.thenMockDml(OperationType.DELETE_DML); + } + + public Mockable allUndeletes() { + return this.thenMockDml(OperationType.UNDELETE_DML); + } + + public Mockable allMerges() { + return this.thenMockDml(OperationType.MERGE_DML); + } + + public Mockable allPublishes() { + return this.thenMockDml(OperationType.PUBLISH_DML); + } + + private Mockable thenMockDml(OperationType dmlType) { + this.mockedDmlTypes.add(dmlType); + return this; + } + + public Mockable insertsFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.INSERT_DML, objectType); + } + + public Mockable updatesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.UPDATE_DML, objectType); + } + + public Mockable upsertsFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.UPSERT_DML, objectType); + } + + public Mockable deletesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.DELETE_DML, objectType); + } + + public Mockable undeletesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.UNDELETE_DML, objectType); + } + + public Mockable mergesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.MERGE_DML, objectType); + } + + public Mockable publishesFor(SObjectType objectType) { + return this.thenMockDmlFor(OperationType.PUBLISH_DML, objectType); + } + + private Mockable thenMockDmlFor(OperationType dmlType, SObjectType objectType) { + if (!this.mockedObjectTypesByDmlType.containsKey(dmlType)) { + this.mockedObjectTypesByDmlType.put(dmlType, new Set()); + } + this.mockedObjectTypesByDmlType.get(dmlType).add(objectType); + return this; + } + + public Mockable exceptionOnInserts() { + return this.thenExceptionOn(OperationType.INSERT_DML); + } + + public Mockable exceptionOnUpdates() { + return this.thenExceptionOn(OperationType.UPDATE_DML); + } + + public Mockable exceptionOnUpserts() { + return this.thenExceptionOn(OperationType.UPSERT_DML); + } + + public Mockable exceptionOnDeletes() { + return this.thenExceptionOn(OperationType.DELETE_DML); + } + + public Mockable exceptionOnUndeletes() { + return this.thenExceptionOn(OperationType.UNDELETE_DML); + } + + public Mockable exceptionOnMerges() { + return this.thenExceptionOn(OperationType.MERGE_DML); + } + + public Mockable exceptionOnPublishes() { + return this.thenExceptionOn(OperationType.PUBLISH_DML); + } + + private Mockable thenExceptionOn(OperationType dmlType) { + this.thrownExceptionDmlTypes.add(dmlType); + return this; + } + + public Mockable exceptionOnInsertsFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.INSERT_DML, objectType); + } + + public Mockable exceptionOnUpdatesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.UPDATE_DML, objectType); + } + + public Mockable exceptionOnUpsertsFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.UPSERT_DML, objectType); + } + + public Mockable exceptionOnDeletesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.DELETE_DML, objectType); + } + + public Mockable exceptionOnUndeletesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.UNDELETE_DML, objectType); + } + + public Mockable exceptionOnMergesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.MERGE_DML, objectType); + } + + public Mockable exceptionOnPublishesFor(SObjectType objectType) { + return this.thenExceptionOnFor(OperationType.PUBLISH_DML, objectType); + } + + private Mockable thenExceptionOnFor(OperationType dmlType, SObjectType objectType) { + if (!this.thrownExceptionDmlTypesByObjectTypes.containsKey(dmlType)) { + this.thrownExceptionDmlTypesByObjectTypes.put(dmlType, new Set()); + } + this.thrownExceptionDmlTypesByObjectTypes.get(dmlType).add(objectType); + return this; + } + + private Boolean shouldBeMocked(OperationType dmlType, SObjectType objectType) { + return this.mockedDmlTypes.contains(dmlType) || + (this.mockedObjectTypesByDmlType.get(dmlType) ?? new Set()).contains(objectType) || + this.shouldThrowException(dmlType, objectType); + } + + private List getMockedRecordResults(DmlStrategy strategy, List recordsToProcess) { + if (this.shouldThrowException(strategy.getOperationType(), strategy.getObjectType())) { + if (strategy.globalConfiguration.allOrNone) { + throw new DmlException('Exception thrown for ' + strategy.getOperationType() + ' operation.'); + } + + // all or none is false, so we need to return a list of record results with errors + return this.getMockedRecordErrors(strategy, recordsToProcess); + } + + return this.getMockedRecordSuccesses(strategy, recordsToProcess); + } + + private List getMockedRecordErrors(DmlStrategy strategy, List recordsToProcess) { + List recordResults = new List(); + String errorMessage = 'Exception thrown for ' + strategy.getOperationType() + ' operation.'; + + for (SObject record : recordsToProcess) { + RecordSummary recordSummary = new RecordSummary() + .isSuccess(false) + .error(new RecordProcessingError().setMessage(errorMessage).setStatusCode(System.StatusCode.ALREADY_IN_PROCESS).setFields(new List{ 'Id' })) + .record(record); + + recordResults.add(recordSummary); + } + + return recordResults; + } + + private List getMockedRecordSuccesses(DmlStrategy strategy, List recordsToProcess) { + List recordResults = new List(); + + for (SObject record : recordsToProcess) { + recordResults.add(strategy.executeMockedDml(record)); + } + + return recordResults; + } + + private Boolean shouldThrowException(OperationType dmlType, SObjectType objectType) { + return this.thrownExceptionDmlTypes.contains(dmlType) || (this.thrownExceptionDmlTypesByObjectTypes.get(dmlType) ?? new Set()).contains(objectType); + } + } + + private class DmlRecords implements Records { + private List records = new List(); + private SObjectType objectType; + + private Map valueByFieldApiName = new Map(); + private List parentRelationships = new List(); + private List externalRelationships = new List(); + + private DmlRecords(List records) { + this.objectType = this.resolveObjectType(records); + + for (SObject record : records) { + this.records.add(new DmlRecord(record)); + } + } + + private SObjectType resolveObjectType(List records) { + if (records == null) { + return null; + } + + SObjectType resolvedType = null; + + for (SObject record : records) { + if (record == null) { + continue; + } + + SObjectType currentType = record.getSObjectType(); + if (resolvedType == null) { + resolvedType = currentType; + continue; + } + + if (resolvedType != currentType) { + throw new DmlException('Mixed SObject types in a single List operation are not supported.'); + } + } + + return resolvedType; + } + + private DmlRecords(Iterable recordIds) { + this.setObjectTypeBasedOnIds(recordIds); + + for (Id recordId : recordIds) { + this.records.add(new DmlRecord(recordId)); + } + } + + private void setObjectTypeBasedOnIds(Iterable recordIds) { + Iterator it = recordIds.iterator(); + if (it.hasNext()) { + this.objectType = it.next().getSObjectType(); + } + } + + public DmlRecords with(SObjectField field, Object value) { + this.valueByFieldApiName.put(field, value); + return this; + } + + public DmlRecords withRelationship(SObjectField relationshipField, SObject relatedToRecord) { + this.parentRelationships.add(new ParentRelationship(relationshipField, new EnhancedRecord(relatedToRecord))); + return this; + } + + public DmlRecords withRelationship(SObjectField relationshipField, SObjectField externalIdField, Object externalId) { + this.externalRelationships.add(new ExternalRelationship(relationshipField, externalIdField, externalId)); + return this; + } + + public List get() { + for (Record record : this.records) { + EnhancedRecord enhancedRecord = record.get(); + + this.populateFieldsValues(enhancedRecord); + this.populateParentRelationships(enhancedRecord); + this.populateExternalRelationships(enhancedRecord); + } + + return this.records; + } + + public SObjectType getSObjectType() { + return this.objectType; + } + + private void populateFieldsValues(EnhancedRecord enhancedRecord) { + for (SObjectField field : this.valueByFieldApiName.keySet()) { + enhancedRecord.with(field, this.valueByFieldApiName.get(field)); + } + } + + private void populateParentRelationships(EnhancedRecord enhancedRecord) { + for (ParentRelationship parentRelationship : this.parentRelationships) { + enhancedRecord.withRelationship(parentRelationship); + } + } + + private void populateExternalRelationships(EnhancedRecord enhancedRecord) { + for (ExternalRelationship externalRelationship : this.externalRelationships) { + enhancedRecord.withRelationship(externalRelationship); + } + } + } + + private class DmlRecord implements Record { + private EnhancedRecord enhancedRecord; + + private DmlRecord(SObject record) { + this.enhancedRecord = new EnhancedRecord(record); + } + + private DmlRecord(Id recordId) { + if (recordId == null) { + throw new DmlException('Invalid argument: recordId. Record ID cannot be null.'); + } + + this.enhancedRecord = new EnhancedRecord(recordId); + } + + public DmlRecord with(SObjectField field, Object value) { + this.enhancedRecord.with(field, value); + return this; + } + + public DmlRecord withRelationship(SObjectField relationshipField, SObject relatedToRecord) { + this.withRelationship(relationshipField, new EnhancedRecord(relatedToRecord)); + return this; + } + + public DmlRecord withRelationship(SObjectField relationshipField, EnhancedRecord enhancedRelatedToRecord) { + this.enhancedRecord.withRelationship(new ParentRelationship(relationshipField, enhancedRelatedToRecord)); + return this; + } + + public DmlRecord withRelationship(SObjectField relationshipField, SObjectField externalIdField, Object externalId) { + this.enhancedRecord.withRelationship(new ExternalRelationship(relationshipField, externalIdField, externalId)); + return this; + } + + public EnhancedRecord get() { + return this.enhancedRecord; + } + + public SObjectType getSObjectType() { + return this.enhancedRecord.getSObjectType(); + } + } + + global class EnhancedRecord { + private SObject currentRecord; + private DmlStrategy strategy; + private Boolean hasTemporaryId = false; + + private List parentRelationships = new List(); + private List externalRelationships = new List(); + + private EnhancedRecord(SObject currentRecord) { + this.currentRecord = currentRecord; + } + + private EnhancedRecord(Id currentRecordId) { + this(currentRecordId?.getSObjectType()?.newSObject(currentRecordId)); + } + + private void with(SObjectField field, Object value) { + this.currentRecord.put(field, value); + } + + private void withRelationship(ParentRelationship parentRelationship) { + this.parentRelationships.add(parentRelationship); + } + + private void withRelationship(ExternalRelationship externalRelationship) { + this.externalRelationships.add(externalRelationship); + } + + private void setDatabaseStrategy(DmlStrategy strategy) { + this.strategy = strategy; + } + + private void addToDependencyGraph(OrderDependencyGraph orderDependencyGraph) { + orderDependencyGraph.ensureRecordIsTracked(this); + + for (ParentRelationship parentRelationship : this.parentRelationships) { + orderDependencyGraph.addDependency(parentRelationship.getRelatedToEnhancedRecord(), this); + } + } + + private void resolveRecordRelationships() { + for (ParentRelationship parentRelationship : this.parentRelationships) { + parentRelationship.resolve(this.currentRecord); + } + + for (ExternalRelationship externalRelationship : this.externalRelationships) { + externalRelationship.resolve(this.currentRecord); + } + } + + private void setTemporaryId() { + if (this.hasId()) { + return; + } + + this.hasTemporaryId = true; + this.currentRecord.Id = randomIdGenerator.get(this.getSObjectType()); + } + + private void clearTemporaryId() { + if (!this.hasTemporaryId) { + return; + } + + this.hasTemporaryId = false; + this.currentRecord.Id = null; + } + + private SObjectType getSObjectType() { + return this.currentRecord?.getSObjectType(); + } + + private SObject getRecord() { + return this.currentRecord; + } + + private Boolean hasId() { + return String.isNotBlank(this.getRecordId()); + } + + private Id getRecordId() { + return this.currentRecord?.Id; + } + } + + private class ParentRelationship { + private SObjectField relationshipField; + private EnhancedRecord relatedToEnhancedRecord; + + private ParentRelationship(SObjectField relationshipField, EnhancedRecord relatedToRecord) { + this.validateRelationshipField(relationshipField); + + this.relationshipField = relationshipField; + this.relatedToEnhancedRecord = relatedToRecord; + } + + private EnhancedRecord getRelatedToEnhancedRecord() { + return this.relatedToEnhancedRecord; + } + + private void resolve(SObject currentRecord) { + currentRecord.put(this.relationshipField, this.relatedToEnhancedRecord.getRecordId()); + } + + private void validateRelationshipField(SObjectField relationshipField) { + if (String.isBlank(relationshipField.getDescribe().getRelationshipName())) { + throw new DmlException('Invalid argument: ' + relationshipField.toString() + '. Field supplied is not a relationship field.'); + } + } + } + + private class ExternalRelationship { + private SObjectField relationshipField; + private SObjectField externalIdField; + private SObjectType relatedToType; + private Object relatedRecordExternalId; + + private ExternalRelationship(SObjectField relationshipField, SObjectField relatedObjectExternalIdField, Object relatedRecordExternalId) { + this.validateRelatedToField(relationshipField); + this.validateExternalIdField(relationshipField, relatedObjectExternalIdField); + + this.relationshipField = relationshipField; + this.relatedToType = relationshipField.getDescribe().getReferenceTo()[0]; + this.externalIdField = relatedObjectExternalIdField; + this.relatedRecordExternalId = relatedRecordExternalId; + } + + private void resolve(SObject currentRecord) { + SObject relationshipObject = this.relatedToType.newSObject(); + relationshipObject.put(this.externalIdField.getDescribe().getName(), this.relatedRecordExternalId); + + currentRecord.putSObject(this.relationshipField.getDescribe().getRelationshipName(), relationshipObject); + } + + private void validateRelatedToField(SObjectField relationshipField) { + if (String.isBlank(relationshipField.getDescribe().getRelationshipName())) { + throw new DmlException('Invalid argument: relationshipField. Field supplied is not a relationship field.'); + } + } + + private void validateExternalIdField(SObjectField relationshipField, SObjectField externalIdField) { + if (!externalIdField.getDescribe().isExternalId()) { + throw new DmlException('Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.'); + } + + SObjectType relatedObjectType = relationshipField.getDescribe().getReferenceTo()[0]; + String externalIdFieldName = externalIdField.getDescribe().getName(); + + Boolean relatedHasExternalIdField = relatedObjectType.getDescribe().fields.getMap().keySet().contains(externalIdFieldName.toLowerCase()); + + if (!relatedHasExternalIdField) { + throw new DmlException('Invalid argument: externalIdField. Field supplied is not a known field on the target sObject.'); + } + } + } + + // Kahn's algorithm for topological sorting of records based on their dependencies + private class OrderDependencyGraph { + private Map> dependentRecordsById = new Map>(); + private Map prerequisiteCountById = new Map(); + + private void ensureRecordIsTracked(EnhancedRecord enhancedRecord) { + Id recordId = enhancedRecord.getRecordId(); + + if (!this.dependentRecordsById.containsKey(recordId)) { + this.dependentRecordsById.put(recordId, new Set()); + } + + if (!this.prerequisiteCountById.containsKey(recordId)) { + this.prerequisiteCountById.put(recordId, 0); + } + } + + private void addDependency(EnhancedRecord prerequisite, EnhancedRecord dependent) { + if (prerequisite.getRecordId() == dependent.getRecordId()) { + throw new DmlException('Self-dependency detected for record: ' + prerequisite.getRecordId()); + } + + if (prerequisite.getRecordId() == null) { + throw new DmlException('Relationship was registered for a record that has not been registered yet.'); + } + + this.ensureRecordIsTracked(prerequisite); + this.ensureRecordIsTracked(dependent); + + Id prerequisiteId = prerequisite.getRecordId(); + Id dependentId = dependent.getRecordId(); + + this.dependentRecordsById.get(prerequisiteId).add(dependentId); + this.prerequisiteCountById.put(dependentId, this.prerequisiteCountById.get(dependentId) + 1); + } + + private List> computeExecutionWaves() { + if (this.prerequisiteCountById.isEmpty()) { + return new List>(); + } + + Integer processedCount = 0; + List readyToProcessIds = this.findRecordsReadyToProcess(); + + List> executionWaves = new List>(); + + while (!readyToProcessIds.isEmpty()) { + List currentWave = readyToProcessIds; + executionWaves.add(currentWave); + + List nextWave = new List(); + + for (Id processedId : currentWave) { + processedCount++; + + for (Id dependentId : (this.dependentRecordsById.get(processedId) ?? new Set())) { + Integer remainingPrerequisites = this.prerequisiteCountById.get(dependentId) - 1; + this.prerequisiteCountById.put(dependentId, remainingPrerequisites); + + if (remainingPrerequisites == 0) { + nextWave.add(dependentId); + } + } + } + + readyToProcessIds = nextWave; + } + + if (processedCount != this.prerequisiteCountById.size()) { + throw new DmlException('Cyclic dependencies detected among records.'); + } + + return executionWaves; + } + + private List findRecordsReadyToProcess() { + List readyToProcess = new List(); + + for (Id recordId : this.prerequisiteCountById.keySet()) { + if (this.prerequisiteCountById.get(recordId) == 0) { + readyToProcess.add(recordId); + } + } + + return readyToProcess; + } + } + + @TestVisible + private class RandomIdGenerator { + private Integer counter = 0; + private Map prefixBySObject = new Map(); + + public Id get(SObjectType objectType) { + return this.get(this.getPrefix(objectType)); + } + + private Id get(String prefix) { + this.counter++; + return Id.valueOf(prefix + '0'.repeat(15 - prefix.length() - String.valueOf(this.counter).length()) + String.valueOf(this.counter)); + } + + private String getPrefix(SObjectType objectType) { + String prefix = this.prefixBySObject.get(objectType); + + if (prefix == null) { + prefix = objectType.getDescribe().getKeyPrefix(); + this.prefixBySObject.put(objectType, prefix); + } + + return prefix; + } + } +} diff --git a/internal/main/default/classes/DML_Full_Test.cls-meta.xml b/package/main/default/classes/DML.cls-meta.xml similarity index 100% rename from internal/main/default/classes/DML_Full_Test.cls-meta.xml rename to package/main/default/classes/DML.cls-meta.xml diff --git a/package/main/default/classes/DML_Full_Test.cls b/package/main/default/classes/DML_Full_Test.cls new file mode 100644 index 0000000..5df54bb --- /dev/null +++ b/package/main/default/classes/DML_Full_Test.cls @@ -0,0 +1,7724 @@ +/** + * Copyright (c) 2026 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev) + * Licensed under the MIT License (https://github.com/beyond-the-cloud-dev/dml-lib/blob/main/LICENSE) + * + * v3.1.0 + * + * PMD False Positives: + * - CyclomaticComplexity: It is a library and we tried to put everything into ONE class + * - CognitiveComplexity: It is a library and we tried to put everything into ONE class + * - ApexUnitTestClassShouldHaveRunAs: System.runAs is used to test fls and sharing modes + * - NcssMethodCount: Some methods are longer because of amount of assertions + * - NcssTypeCount: It is a library and we tried to put everything into ONE class + **/ +@SuppressWarnings('PMD.CyclomaticComplexity,PMD.CognitiveComplexity,PMD.ApexUnitTestClassShouldHaveRunAs,PMD.NcssMethodCount,PMD.NcssTypeCount') +@IsTest +private class DML_Full_Test { + @IsTest + static void toInsertSingleRecord() { + // Setup + Account account1 = getAccount(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toInsert(account1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be inserted.'); + + Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); + + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(operationResult.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void toInsertWithRelationshipSingleRecord() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.AccountId, newAccount)).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted.'); + + Assert.areEqual(newAccount.Id, newContact.AccountId, 'Contact should be related to Account.'); + } + + @IsTest + static void toInsertWithDifferentRecordTypesAndRelationships() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + Account account3 = getAccount(3); + Contact contact1 = getContact(1); + Opportunity opportunity1 = getOpportunity(1); + Lead lead1 = getLead(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDmlStatements(); + + DML.Result result = new DML() + .toInsert(account1) + .toInsert(account2) + .toInsert(DML.Record(account3).withRelationship(Account.ParentId, account2)) + .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account2)) + .toInsert(opportunity1) + .toInsert(lead1) + .commitWork(); + + Integer dmlsAfter = Limits.getDmlStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(dmlsAfter - dmlsBefore, 5, '5 DML statements should be executed'); + + Assert.areEqual(3, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Lead should be inserted.'); + + Assert.areEqual(account2.Id, contact1.AccountId, 'Contact should be related to Account 2.'); + Assert.areEqual(account2.Id, account3.ParentId, 'Account 3 should be related to Account 2.'); + + Assert.areEqual(4, result.inserts().size(), 'Inserted operation result should contain 4 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(3, operationResult.records().size(), 'Inserted operation result should contain the inserted records.'); + Assert.areEqual(3, operationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(operationResult.hasFailures(), 'Inserted operation result should not have failures.'); + + DML.OperationResult contactOperationResult = result.insertsOf(Contact.SObjectType); + + Assert.areEqual(1, contactOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, contactOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Contact.SObjectType, contactOperationResult.objectType(), 'Inserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, contactOperationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(contactOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); + + DML.OperationResult opportunityOperationResult = result.insertsOf(Opportunity.SObjectType); + + Assert.areEqual(1, opportunityOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Inserted operation result should contain Opportunity object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, opportunityOperationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(opportunityOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); + + DML.OperationResult leadOperationResult = result.insertsOf(Lead.SObjectType); + + Assert.areEqual(1, leadOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Inserted operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, leadOperationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(leadOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void toInsertMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toInsert(account1).toInsert(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); + + Assert.isNotNull(account1.Id, 'Account 1 should be inserted and have an Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); + + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result, because 2 Account records are grouped.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(2, operationResult.records().size(), 'Inserted operation result should contain the inserted records.'); + Assert.areEqual(2, operationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(operationResult.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void toInsertWithRelationshipMultipleRecords() { + // Setup + Account newAccount = getAccount(1); + Contact newContact1 = getContact(1); + Contact newContact2 = getContact(2); + + List contacts = new List{ newContact1, newContact2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.AccountId, newAccount)).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.areEqual(2, [SELECT COUNT() FROM Contact], 'Contacts should be inserted.'); + + Assert.areEqual(newAccount.Id, newContact1.AccountId, 'Contact should be related to Account.'); + Assert.areEqual(newAccount.Id, newContact2.AccountId, 'Contact 2 should be related to Account.'); + } + + @IsTest + static void toInsertMultipleRecordsTypes() { + // Setup + Account account1 = getAccount(1); + Opportunity opportunityToInsert = getOpportunity(1); + Lead leadToInsert = getLead(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toInsert(account1).toInsert(opportunityToInsert).toInsert(leadToInsert).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Lead should be inserted.'); + + Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); + Assert.isNotNull(opportunityToInsert.Id, 'Opportunity should be inserted and have an Id.'); + Assert.isNotNull(leadToInsert.Id, 'Lead should be inserted and have an Id.'); + + Assert.areEqual(3, result.inserts().size(), 'Inserted operation result should contain 3 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult accountOperationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(1, accountOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, accountOperationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(accountOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); + + DML.OperationResult opportunityOperationResult = result.insertsOf(Opportunity.SObjectType); + + Assert.areEqual(1, opportunityOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Inserted operation result should contain Opportunity object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, opportunityOperationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(opportunityOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); + + DML.OperationResult leadOperationResult = result.insertsOf(Lead.SObjectType); + + Assert.areEqual(1, leadOperationResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Inserted operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, leadOperationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(leadOperationResult.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void toInsertListOfRecords() { + // Setup + List accounts = new List{ getAccount(1), getAccount(2), getAccount(3) }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(accounts).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(3, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); + + Assert.isNotNull(accounts[0].Id, 'Account 1 should be inserted and have an Id.'); + Assert.isNotNull(accounts[1].Id, 'Account 2 should be inserted and have an Id.'); + Assert.isNotNull(accounts[2].Id, 'Account 3 should be inserted and have an Id.'); + } + + @IsTest + static void toInsertWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toInsert(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 1 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void insertImmediatelySingleRecord() { + // Setup + Account account1 = getAccount(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().insertImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be inserted.'); + Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); + Assert.areEqual(1, result.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(result.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void insertImmediatelyMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().insertImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); + + Assert.isNotNull(account1.Id, 'Account 1 should be inserted and have an Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); + + Assert.areEqual(2, result.records().size(), 'Inserted operation result should contain the inserted records.'); + Assert.areEqual(2, result.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(result.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void insertImmediatelyWithDmlRecord() { + // Setup + Account account1 = getAccount(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().insertImmediately(DML.Record(account1).with(Account.Name, 'DML Record Account')); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be inserted.'); + Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); + Assert.areEqual('DML Record Account', [SELECT Name FROM Account WHERE Id = :account1.Id].Name, 'Account name should be set.'); + Assert.areEqual(1, result.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(result.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void insertImmediatelyWithDmlRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().insertImmediately(DML.Records(new List{ account1, account2 }).with(Account.Name, 'DML Records Account')); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); + + Assert.isNotNull(account1.Id, 'Account 1 should be inserted and have an Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account WHERE Name = 'DML Records Account'], 'Account names should be set.'); + + Assert.areEqual(2, result.records().size(), 'Inserted operation result should contain the inserted records.'); + Assert.areEqual(2, result.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(result.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void toUpdateSingleRecord() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account'; + + DML.Result result = new DML().toUpdate(account1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); + + Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(operationResult.hasFailures(), 'Updated operation result should not have failures.'); + } + + @IsTest + static void toUpdateSingleRecordTwiceWithMergeOnDuplicate() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + account1.Name = 'Updated Test Account'; + account1.Website = 'https://www.updatedtestaccount.com'; + + DML db = new DML(); + + db.combineOnDuplicate(); + + db.toUpdate(account1); + + Account duplicatedAccount = new Account(Id = account1.Id, Name = 'Updated Test Account 2', Description = 'Updated Test Description'); + + db.toUpdate(duplicatedAccount); + + DML.Result result = db.commitWork(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); + + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(operationResult.hasFailures(), 'Updated operation result should not have failures.'); + Assert.areEqual('Updated Test Account 2', account1.Name, 'Account should be updated.'); + Assert.areEqual('https://www.updatedtestaccount.com', account1.Website, 'Account should be updated.'); + Assert.areEqual('Updated Test Description', account1.Description, 'Account should be updated.'); + } + + @IsTest + static void toUpdateWithEmptyRecordIds() { + // Setup + List recordIds = new List(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUpdate(DML.Records(recordIds)).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 result.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toUpdateWithRelationshipSingleRecord() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + insert newContact; + + // Test + Test.startTest(); + new DML().toInsert(newAccount).toUpdate(DML.Record(newContact).withRelationship(Contact.AccountId, newAccount)).commitWork(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be updated.'); + + List contacts = [SELECT Id, AccountId FROM Contact WHERE Id = :newContact.Id]; + + Assert.areEqual(newAccount.Id, contacts[0].AccountId, 'Contact should be related to Account.'); + } + + @IsTest + static void toUpdateMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + insert new List{ account1, account2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account 1'; + account2.Name = 'Updated Test Account 2'; + + DML.Result result = new DML().toUpdate(account1).toUpdate(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be updated.'); + + Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(2, operationResult.records().size(), 'Updated operation result should contain the updated records.'); + Assert.areEqual(2, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(operationResult.hasFailures(), 'Updated operation result should not have failures.'); + } + + @IsTest + static void toUpdateWithRelationshipMultipleRecords() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + Contact newContact2 = getContact(2); + + List contactsToCreate = new List{ newContact, newContact2 }; + insert contactsToCreate; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(newAccount).toUpdate(DML.Records(contactsToCreate).withRelationship(Contact.AccountId, newAccount)).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed.'); + + Assert.areEqual(2, [SELECT COUNT() FROM Contact], 'Contacts should be updated.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + + List contacts = [SELECT Id, AccountId FROM Contact WHERE Id IN :contactsToCreate]; + + Assert.areEqual(newAccount.Id, contacts[0].AccountId, 'Contact should be related to Account.'); + Assert.areEqual(newAccount.Id, contacts[1].AccountId, 'Contact 2 should be related to Account.'); + } + + @IsTest + static void toUpdateMultipleRecordsTypes() { + // Setup + Account account1 = getAccount(1); + Opportunity opportunity1 = getOpportunity(1); + Lead lead1 = getLead(1); + + insert new List{ account1, opportunity1, lead1 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account'; + opportunity1.Name = 'Updated Test Opportunity'; + lead1.FirstName = 'Updated Test'; + + DML.Result result = new DML().toUpdate(account1).toUpdate(opportunity1).toUpdate(lead1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); + Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be updated.'); + Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Lead should be updated.'); + + Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); + Assert.areEqual('Updated Test Opportunity', opportunity1.Name, 'Opportunity should be updated.'); + Assert.areEqual('Updated Test', lead1.FirstName, 'Lead should be updated.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(3, result.updates().size(), 'Updated operation result should contain 3 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult accountOperationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, accountOperationResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, accountOperationResult.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(accountOperationResult.hasFailures(), 'Updated operation result should not have failures.'); + + DML.OperationResult opportunityOperationResult = result.updatesOf(Opportunity.SObjectType); + + Assert.areEqual(1, opportunityOperationResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Updated operation result should contain Opportunity object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, opportunityOperationResult.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(opportunityOperationResult.hasFailures(), 'Updated operation result should not have failures.'); + + DML.OperationResult leadOperationResult = result.updatesOf(Lead.SObjectType); + + Assert.areEqual(1, leadOperationResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Updated operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, leadOperationResult.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(leadOperationResult.hasFailures(), 'Updated operation result should not have failures.'); + } + + @IsTest + static void toUpdateListOfRecords() { + // Setup + List accounts = new List{ getAccount(1), getAccount(2), getAccount(3) }; + insert accounts; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + accounts[0].Name = 'Updated Test Account 1'; + accounts[1].Name = 'Updated Test Account 2'; + accounts[2].Name = 'Updated Test Account 3'; + + new DML().toUpdate(accounts).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(3, [SELECT COUNT() FROM Account], 'Accounts should be updated.'); + + Assert.areEqual('Updated Test Account 1', accounts[0].Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Account 2', accounts[1].Name, 'Account 2 should be updated.'); + Assert.areEqual('Updated Test Account 3', accounts[2].Name, 'Account 3 should be updated.'); + } + + @IsTest + static void toUpdateWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUpdate(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void updateImmediatelySingleRecord() { + // Setup + Account account1 = getAccount(1); + insert account1; + account1.Name = 'Updated Test Account'; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().updateImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account WHERE Name = 'Updated Test Account'], 'Single record should be updated.'); + Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); + Assert.areEqual(1, result.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(result.hasFailures(), 'Updated operation result should not have failures.'); + } + + @IsTest + static void updateImmediatelyMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + insert new List{ account1, account2 }; + + account1.Name = 'Updated Test Account 1'; + account2.Name = 'Updated Test Account 2'; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().updateImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account WHERE Name IN ('Updated Test Account 1', 'Updated Test Account 2')], 'Accounts should be updated.'); + + Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); + + Assert.areEqual(2, result.records().size(), 'Updated operation result should contain the updated records.'); + Assert.areEqual(2, result.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(result.hasFailures(), 'Updated operation result should not have failures.'); + } + + @IsTest + static void updateImmediatelyWithDmlRecord() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().updateImmediately(DML.Record(account1).with(Account.Name, 'Updated DML Record Account')); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual('Updated DML Record Account', [SELECT Name FROM Account WHERE Id = :account1.Id].Name, 'Account name should be updated.'); + Assert.areEqual(1, result.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(result.hasFailures(), 'Updated operation result should not have failures.'); + } + + @IsTest + static void updateImmediatelyWithDmlRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + insert new List{ account1, account2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().updateImmediately(DML.Records(new List{ account1, account2 }).with(Account.Name, 'Updated DML Records Account')); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account WHERE Name = 'Updated DML Records Account'], 'Account names should be updated.'); + + Assert.areEqual(2, result.records().size(), 'Updated operation result should contain the updated records.'); + Assert.areEqual(2, result.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(result.hasFailures(), 'Updated operation result should not have failures.'); + } + + @IsTest + static void toUpsertSingleExistingRecord() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account'; + + DML.Result result = new DML().toUpsert(account1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); + + Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.isFalse(operationResult.hasFailures(), 'Upserted operation result should not have failures.'); + } + + @IsTest + static void toUpsertSingleNewRecord() { + // Setup + Account account1 = getAccount(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUpsert(account1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + + Assert.isNotNull(account1.Id, 'Account should be inserted and have an Id.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.isFalse(operationResult.hasFailures(), 'Upserted operation result should not have failures.'); + } + + @IsTest + static void toUpsertMultipleExistingRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + insert new List{ account1, account2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account 1'; + account2.Name = 'Updated Test Account 2'; + + new DML().toUpsert(account1).toUpsert(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be updated.'); + + Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); + } + + @IsTest + static void toUpsertMultipleNewRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toUpsert(account1).toUpsert(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); + + Assert.isNotNull(account1.Id, 'Account 1 should be inserted and have an Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); + } + + @IsTest + static void toUpsertExistingAndNewRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account 1'; + + new DML().toUpsert(account1).toUpsert(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Both accounts should be upserted.'); + + Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); + } + + @IsTest + static void toUpsertListOfRecords() { + // Setup + List existingAccounts = new List{ getAccount(1), getAccount(2), getAccount(3) }; + insert existingAccounts; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + existingAccounts[0].Name = 'Updated Test Account 1'; + existingAccounts[1].Name = 'Updated Test Account 2'; + existingAccounts[2].Name = 'Updated Test Account 3'; + + List newAccounts = new List{ getAccount(1), getAccount(2), getAccount(3) }; + + new DML().toUpsert(existingAccounts).toUpsert(newAccounts).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(6, [SELECT COUNT() FROM Account], 'Accounts should be updated and inserted.'); + + Assert.areEqual('Updated Test Account 1', existingAccounts[0].Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Account 2', existingAccounts[1].Name, 'Account 2 should be updated.'); + Assert.areEqual('Updated Test Account 3', existingAccounts[2].Name, 'Account 3 should be updated.'); + + Assert.isNotNull(newAccounts[0].Id, 'New Account 1 should be inserted and have an Id.'); + Assert.isNotNull(newAccounts[1].Id, 'New Account 2 should be inserted and have an Id.'); + Assert.isNotNull(newAccounts[2].Id, 'New Account 3 should be inserted and have an Id.'); + } + + @IsTest + static void toUpsertWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUpsert(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void upsertImmediatelySingleRecord() { + // Setup + Account account1 = getAccount(1); + insert account1; + account1.Name = 'Upserted Test Account'; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().upsertImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account WHERE Name = 'Upserted Test Account'], 'Single record should be upserted.'); + Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); + Assert.areEqual(1, result.records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, result.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.isFalse(result.hasFailures(), 'Upserted operation result should not have failures.'); + } + + @IsTest + static void upsertImmediatelyMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + insert account1; // Make account1 existing for upsert + + account1.Name = 'Upserted Test Account 1'; + // account2 is new for upsert + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().upsertImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be upserted.'); + + Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); + + Assert.areEqual(2, result.records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(2, result.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.isFalse(result.hasFailures(), 'Upserted operation result should not have failures.'); + } + + @IsTest + static void upsertImmediatelyWithDmlRecord() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().upsertImmediately(DML.Record(account1).with(Account.Name, 'Upserted DML Record Account')); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual('Upserted DML Record Account', [SELECT Name FROM Account WHERE Id = :account1.Id].Name, 'Account name should be upserted.'); + Assert.areEqual(1, result.records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, result.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.isFalse(result.hasFailures(), 'Upserted operation result should not have failures.'); + } + + @IsTest + static void upsertImmediatelyWithDmlRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + insert account1; // Make account1 existing for upsert + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().upsertImmediately(DML.Records(new List{ account1, account2 }).with(Account.Name, 'Upserted DML Records Account')); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be upserted.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account WHERE Name = 'Upserted DML Records Account'], 'Account names should be set.'); + + Assert.isNotNull(account2.Id, 'Account 2 should be inserted and have an Id.'); + + Assert.areEqual(2, result.records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(2, result.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.isFalse(result.hasFailures(), 'Upserted operation result should not have failures.'); + } + + @IsTest + static void toDeleteSingleRecordById() { + // Setup + Account account = insertAccount(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toDelete(account.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); + } + + @IsTest + static void toDeleteSingleRecord() { + // Setup + Account account1 = insertAccount(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toDelete(account1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(operationResult.hasFailures(), 'Deleted operation result should not have failures.'); + } + + @IsTest + static void toDeleteMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + insert new List{ account1, account2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toDelete(account1).toDelete(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(2, operationResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, operationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(operationResult.hasFailures(), 'Deleted operation result should not have failures.'); + } + + @IsTest + static void toDeleteListOfRecords() { + // Setup + List accounts = insertAccounts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toDelete(accounts).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + } + + @IsTest + static void toDeleteMultipleRecordsById() { + // Setup + List accounts = insertAccounts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + Set accountIds = new Map(accounts).keySet(); + + new DML().toDelete(accountIds).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + } + + @IsTest + static void toDeleteMultipleRecordsTypes() { + // Setup + Account account1 = getAccount(1); + Opportunity opportunity1 = getOpportunity(1); + Lead lead1 = getLead(1); + + insert new List{ account1, opportunity1, lead1 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toDelete(account1).toDelete(opportunity1).toDelete(lead1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); + Assert.areEqual(0, [SELECT COUNT() FROM Opportunity], 'Opportunity should be deleted.'); + Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'Lead should be deleted.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(3, result.deletes().size(), 'Deleted operation result should contain 3 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult accountOperationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, accountOperationResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountOperationResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(accountOperationResult.hasFailures(), 'Deleted operation result should not have failures.'); + + DML.OperationResult opportunityOperationResult = result.deletesOf(Opportunity.SObjectType); + + Assert.areEqual(1, opportunityOperationResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Deleted operation result should contain Opportunity object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, opportunityOperationResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(opportunityOperationResult.hasFailures(), 'Deleted operation result should not have failures.'); + + DML.OperationResult leadOperationResult = result.deletesOf(Lead.SObjectType); + + Assert.areEqual(1, leadOperationResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Deleted operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, leadOperationResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(leadOperationResult.hasFailures(), 'Deleted operation result should not have failures.'); + } + + @IsTest + static void toDeleteWithoutExistingIds() { + // Setup + Account account = getAccount(1); + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toDelete(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only existing records can be registered as deleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toDeleteWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toDelete(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void deleteImmediatelySingleRecord() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().deleteImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Single record should be deleted.'); + Assert.areEqual(1, result.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(result.hasFailures(), 'Deleted operation result should not have failures.'); + } + + @IsTest + static void deleteImmediatelySingleRecordById() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().deleteImmediately(account1.Id); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Single record should be deleted.'); + Assert.areEqual(1, result.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(result.hasFailures(), 'Deleted operation result should not have failures.'); + } + + @IsTest + static void deleteImmediatelyMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + insert new List{ account1, account2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().deleteImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + + Assert.areEqual(2, result.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, result.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(result.hasFailures(), 'Deleted operation result should not have failures.'); + } + + @IsTest + static void deleteImmediatelyMultipleRecordsByIds() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + insert new List{ account1, account2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.OperationResult result = new DML().deleteImmediately(new List{ account1.Id, account2.Id }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + + Assert.areEqual(2, result.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, result.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(result.hasFailures(), 'Deleted operation result should not have failures.'); + } + + @IsTest + static void toHardDeleteSingleRecordById() { + // Setup + Account account = insertAccount(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toHardDelete(account.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be hard deleted.'); + // No assertion with ALL ROWS, because there is Salesforce error + } + + @IsTest + static void toHardDeleteSingleRecord() { + // Setup + Account account1 = insertAccount(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toHardDelete(account1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be hard deleted.'); + // No assertion with ALL ROWS, because there is Salesforce error + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Hard deleted operation result should contain the deleted record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Hard deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Hard deleted operation result should contain delete type.'); + Assert.isFalse(operationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); + } + + @IsTest + static void toHardDeleteMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + insert new List{ account1, account2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toHardDelete(account1).toHardDelete(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be hard deleted.'); + // No assertion with ALL ROWS, because there is Salesforce error + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(2, operationResult.records().size(), 'Hard deleted operation result should contain the deleted records.'); + Assert.areEqual(2, operationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Hard deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Hard deleted operation result should contain delete type.'); + Assert.isFalse(operationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); + } + + @IsTest + static void toHardDeleteListOfRecords() { + // Setup + List accounts = insertAccounts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toHardDelete(accounts).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be hard deleted.'); + // No assertion with ALL ROWS, because there is Salesforce error + } + + @IsTest + static void toHardDeleteMultipleRecordsById() { + // Setup + List accounts = insertAccounts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + Set accountIds = new Map(accounts).keySet(); + + new DML().toHardDelete(accountIds).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be hard deleted.'); + // No assertion with ALL ROWS, because there is Salesforce error + } + + @IsTest + static void toHardDeleteMultipleRecordsTypes() { + // Setup + Account account1 = getAccount(1); + Opportunity opportunity1 = getOpportunity(1); + Lead lead1 = getLead(1); + + insert new List{ account1, opportunity1, lead1 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toHardDelete(account1).toHardDelete(opportunity1).toHardDelete(lead1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(6, dmlsAfter - dmlsBefore, '6 DML statements should be executed (3 deletes + 3 hard deletes).'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be hard deleted.'); + Assert.areEqual(0, [SELECT COUNT() FROM Opportunity], 'Opportunity should be hard deleted.'); + Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'Lead should be hard deleted.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(3, result.deletes().size(), 'Deleted operation result should contain 3 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult accountOperationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, accountOperationResult.records().size(), 'Hard deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Hard deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountOperationResult.operationType(), 'Hard deleted operation result should contain delete type.'); + Assert.isFalse(accountOperationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); + + DML.OperationResult opportunityOperationResult = result.deletesOf(Opportunity.SObjectType); + + Assert.areEqual(1, opportunityOperationResult.records().size(), 'Hard deleted operation result should contain the deleted record.'); + Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); + Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Hard deleted operation result should contain Opportunity object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, opportunityOperationResult.operationType(), 'Hard deleted operation result should contain delete type.'); + Assert.isFalse(opportunityOperationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); + + DML.OperationResult leadOperationResult = result.deletesOf(Lead.SObjectType); + + Assert.areEqual(1, leadOperationResult.records().size(), 'Hard deleted operation result should contain the deleted record.'); + Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Hard deleted operation result should contain the deleted record results.'); + Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Hard deleted operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, leadOperationResult.operationType(), 'Hard deleted operation result should contain delete type.'); + Assert.isFalse(leadOperationResult.hasFailures(), 'Hard deleted operation result should not have failures.'); + } + + @IsTest + static void toHardDeleteWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toHardDelete(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toHardDeleteWithoutExistingIds() { + // Setup + Account account = getAccount(1); + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toHardDelete(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only existing records can be registered as deleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toUndeleteSingleRecordById() { + // Setup + Account account = insertAccount(); + delete account; + + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUndelete(account.Id).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be undeleted.'); + } + + @IsTest + static void toUndeleteSingleRecord() { + // Setup + Account account1 = insertAccount(); + delete account1; + + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toUndelete(account1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be undeleted.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(operationResult.hasFailures(), 'Undeleted operation result should not have failures.'); + } + + @IsTest + static void toUndeleteMultipleRecordsById() { + // Setup + List accounts = insertAccounts(); + delete accounts; + + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + Set accountIds = new Map(accounts).keySet(); + + new DML().toUndelete(accountIds).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(accounts.size(), [SELECT COUNT() FROM Account], 'Accounts should be undeleted.'); + } + + @IsTest + static void toUndeleteMultipleRecords() { + // Setup + List accounts = insertAccounts(); + delete accounts; + + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toUndelete(accounts).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(accounts.size(), [SELECT COUNT() FROM Account], 'Accounts should be undeleted.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(3, operationResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(3, operationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(operationResult.hasFailures(), 'Undeleted operation result should not have failures.'); + } + + @IsTest + static void toUndeleteMultipleRecordsTypes() { + // Setup + Account account1 = getAccount(1); + Opportunity opportunity1 = getOpportunity(1); + Lead lead1 = getLead(1); + + insert new List{ account1, opportunity1, lead1 }; + delete new List{ account1, opportunity1, lead1 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toUndelete(account1).toUndelete(opportunity1).toUndelete(lead1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be undeleted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be undeleted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Lead should be undeleted.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(3, result.undeletes().size(), 'Undeleted operation result should contain 3 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult accountOperationResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(1, accountOperationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, accountOperationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Account.SObjectType, accountOperationResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountOperationResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(accountOperationResult.hasFailures(), 'Undeleted operation result should not have failures.'); + + DML.OperationResult opportunityOperationResult = result.undeletesOf(Opportunity.SObjectType); + + Assert.areEqual(1, opportunityOperationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, opportunityOperationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Opportunity.SObjectType, opportunityOperationResult.objectType(), 'Undeleted operation result should contain Opportunity object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, opportunityOperationResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(opportunityOperationResult.hasFailures(), 'Undeleted operation result should not have failures.'); + + DML.OperationResult leadOperationResult = result.undeletesOf(Lead.SObjectType); + + Assert.areEqual(1, leadOperationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, leadOperationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Lead.SObjectType, leadOperationResult.objectType(), 'Undeleted operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, leadOperationResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(leadOperationResult.hasFailures(), 'Undeleted operation result should not have failures.'); + } + + @IsTest + static void toUndeleteWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toUndelete(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void undeleteImmediatelySingleRecord() { + // Setup + Account account1 = getAccount(1); + insert account1; + delete account1; + + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult result = new DML().undeleteImmediately(account1); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be undeleted.'); + Assert.areEqual(1, result.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(result.hasFailures(), 'Undeleted operation result should not have failures.'); + } + + @IsTest + static void undeleteImmediatelySingleRecordById() { + // Setup + Account account1 = getAccount(1); + insert account1; + delete account1; + + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should be deleted.'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult result = new DML().undeleteImmediately(account1.Id); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Single record should be undeleted.'); + Assert.areEqual(1, result.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(result.hasFailures(), 'Undeleted operation result should not have failures.'); + } + + @IsTest + static void undeleteImmediatelyMultipleRecords() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + insert new List{ account1, account2 }; + delete new List{ account1, account2 }; + + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult result = new DML().undeleteImmediately(new List{ account1, account2 }); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be undeleted.'); + + Assert.areEqual(2, result.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, result.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(result.hasFailures(), 'Undeleted operation result should not have failures.'); + } + + @IsTest + static void undeleteImmediatelyMultipleRecordsByIds() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + insert new List{ account1, account2 }; + delete new List{ account1, account2 }; + + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult result = new DML().undeleteImmediately(new List{ account1.Id, account2.Id }); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be undeleted.'); + + Assert.areEqual(2, result.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, result.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Account.SObjectType, result.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(result.hasFailures(), 'Undeleted operation result should not have failures.'); + } + + @IsTest + static void toMergeSingleDuplicateRecord() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + insert new List{ masterAccount, duplicateAccount }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toMerge(masterAccount, duplicateAccount).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); + Assert.areEqual(masterAccount.Id, [SELECT Id FROM Account LIMIT 1].Id, 'Master account should survive the merge.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); + Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); + } + + @IsTest + static void toMergeSingleDuplicateRecordById() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + insert new List{ masterAccount, duplicateAccount }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toMerge(masterAccount, duplicateAccount.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); + Assert.areEqual(masterAccount.Id, [SELECT Id FROM Account LIMIT 1].Id, 'Master account should survive the merge.'); + + Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); + Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); + } + + @IsTest + static void toMergeMultipleDuplicateRecords() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount1 = getAccount(2); + Account duplicateAccount2 = getAccount(3); + insert new List{ masterAccount, duplicateAccount1, duplicateAccount2 }; + + List duplicateAccounts = new List{ duplicateAccount1, duplicateAccount2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toMerge(masterAccount, duplicateAccounts).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); + Assert.areEqual(masterAccount.Id, [SELECT Id FROM Account LIMIT 1].Id, 'Master account should survive the merge.'); + + Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(2, operationResult.records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); + Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); + } + + @IsTest + static void toMergeMultipleDuplicateRecordsById() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount1 = getAccount(2); + Account duplicateAccount2 = getAccount(3); + insert new List{ masterAccount, duplicateAccount1, duplicateAccount2 }; + + Set duplicateAccountIds = new Set{ duplicateAccount1.Id, duplicateAccount2.Id }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toMerge(masterAccount, duplicateAccountIds).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); + Assert.areEqual(masterAccount.Id, [SELECT Id FROM Account LIMIT 1].Id, 'Master account should survive the merge.'); + + Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(2, operationResult.records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); + Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); + } + + @IsTest + static void toMergeWithRelatedRecords() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + insert new List{ masterAccount, duplicateAccount }; + + Contact contactOnDuplicate = getContact(1); + contactOnDuplicate.AccountId = duplicateAccount.Id; + insert contactOnDuplicate; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toMerge(masterAccount, duplicateAccount).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should still exist.'); + + Contact reparentedContact = [SELECT Id, AccountId FROM Contact WHERE Id = :contactOnDuplicate.Id]; + Assert.areEqual(masterAccount.Id, reparentedContact.AccountId, 'Contact should be reparented to master account.'); + } + + @IsTest + static void toMergeWithoutExistingMasterRecord() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + insert duplicateAccount; + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toMerge(masterAccount, duplicateAccount).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only existing records can be merged.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toMergeWithoutExistingDuplicateRecord() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + insert masterAccount; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toMerge(masterAccount, duplicateAccount).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toMergeLeads() { + // Setup + Lead masterLead = getLead(1); + Lead duplicateLead = getLead(2); + insert new List{ masterLead, duplicateLead }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toMerge(masterLead, duplicateLead).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Lead], 'Only master lead should remain after merge.'); + Assert.areEqual(masterLead.Id, [SELECT Id FROM Lead LIMIT 1].Id, 'Master lead should survive the merge.'); + + Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); + + DML.OperationResult operationResult = result.mergesOf(Lead.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(Lead.SObjectType, operationResult.objectType(), 'Merged operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); + Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); + } + + @IsTest + static void toMergeContacts() { + // Setup + Contact masterContact = getContact(1); + Contact duplicateContact = getContact(2); + insert new List{ masterContact, duplicateContact }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toMerge(masterContact, duplicateContact).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Only master contact should remain after merge.'); + Assert.areEqual(masterContact.Id, [SELECT Id FROM Contact LIMIT 1].Id, 'Master contact should survive the merge.'); + + Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); + + DML.OperationResult operationResult = result.mergesOf(Contact.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(Contact.SObjectType, operationResult.objectType(), 'Merged operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); + Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); + } + + @IsTest + static void toPublishSingleRecord() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toPublish(event).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + + DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Published operation result should contain publish type.'); + Assert.isTrue(operationResult.hasFailures(), 'Published operation result should have failures.'); + } + + @IsTest + static void toPublishMultipleRecords() { + // Setup + List events = new List{ new FlowOrchestrationEvent(), new FlowOrchestrationEvent() }; + + Exception expectedException = null; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = null; + try { + result = new DML().toPublish(events).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.isNull(expectedException, 'Expected exception should not be thrown.'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + + DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); + + Assert.areEqual(2, operationResult.records().size(), 'Published operation result should contain the published records.'); + Assert.areEqual(2, operationResult.recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Published operation result should contain publish type.'); + Assert.isTrue(operationResult.hasFailures(), 'Published operation result should have failures.'); + } + + @IsTest + static void toPublishWithEmptyRecords() { + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toPublish(new List()).commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 1 result.'); + } + + @IsTest + static void publishImmediatelySingleRecord() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult result = new DML().publishImmediately(event); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, result.records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, result.recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, result.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, result.operationType(), 'Published operation result should contain publish type.'); + } + + @IsTest + static void publishImmediatelyMultipleRecords() { + // Setup + List events = new List{ new FlowOrchestrationEvent(), new FlowOrchestrationEvent() }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.OperationResult operationResult = new DML().publishImmediately(events); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, operationResult.records().size(), 'Published operation result should contain the published records.'); + Assert.areEqual(2, operationResult.recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Published operation result should contain publish type.'); + } + + @IsTest + static void dryRun() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account).dryRun(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted.'); + Assert.areEqual( + 4, + dmlStatementsAfter - dmlStatementsBefore, + 'DML statements should be 4, because one for savepoint, one for rollback, one for release savepoint, and one for the insert.' + ); + } + + @IsTest + static void dryRunWhenExceptionIsThrown() { + // Setup + Account account = getAccount(1); + account.Name = null; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account).dryRun(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); + } + + @SuppressWarnings('PMD.ApexUnitTestClassShouldHaveAsserts') + @IsTest + static void preview() { + // Setup + Account account = getAccount(1); + + // Test + new DML().toInsert(account).preview(); + } + + // CONFIGURATION + + @IsTest + static void commitTransaction() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account).commitTransaction(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.isTrue(dmlStatementsAfter - dmlStatementsBefore >= 3, 'DML statements should be greater than or equal to 3, because savepoint was set and rollback was called.'); + } + + @IsTest + static void commitTransactionWhenExceptionIsThrown() { + // Setup + Account account = getAccount(1); + account.Name = null; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account).toInsert(account).commitTransaction(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); + } + + @IsTest + static void commitTransactionWhenAllOrNoneIsFalse() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(account).allowPartialSuccess().commitTransaction(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception should be thrown.'); + Assert.areEqual('commitTransaction() is not supported when allOrNone=false', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void discardWork() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toInsert(account).discardWork().commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 0, because work was discarded.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted.'); + + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + } + + @IsTest + static void singleSObjectRecordWithValue() { + // Setup + Account newAccount = getAccount(1); + + // Test + Test.startTest(); + new DML() + .toInsert(DML.Record(newAccount).with(Account.Name, 'New Test Account').with(Account.Industry, 'New Test Industry').with(Account.Description, 'New Test Description')) + .commitWork(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + + List accounts = [SELECT Id, Name, Industry, Description FROM Account LIMIT 1]; + + Assert.areEqual('New Test Account', accounts[0].Name, 'Account name should be "New Test Account".'); + Assert.areEqual('New Test Industry', accounts[0].Industry, 'Account industry should be "New Test Industry".'); + Assert.areEqual('New Test Description', accounts[0].Description, 'Account description should be "New Test Description".'); + } + + @IsTest + static void singleIdRecordWithValue() { + // Setup + Account newAccount = getAccount(1); + insert newAccount; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML() + .toUpdate( + DML.Record(newAccount.Id).with(Account.Name, 'New Test Account').with(Account.Industry, 'New Test Industry').with(Account.Description, 'New Test Description') + ) + .commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be updated.'); + + List accounts = [SELECT Id, Name, Industry, Description FROM Account LIMIT 1]; + + Assert.areEqual('New Test Account', accounts[0].Name, 'Account name should be "New Test Account".'); + Assert.areEqual('New Test Industry', accounts[0].Industry, 'Account industry should be "New Test Industry".'); + Assert.areEqual('New Test Description', accounts[0].Description, 'Account description should be "New Test Description".'); + } + + @IsTest + static void multipleSObjectRecordsWithValue() { + // Setup + Account newAccount1 = getAccount(1); + Account newAccount2 = getAccount(2); + + List newAccounts = new List{ newAccount1, newAccount2 }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toInsert(DML.Records(newAccounts).with(Account.Industry, 'New Test Industry').with(Account.Description, 'New Test Description')).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Accounts should be inserted.'); + + List accounts = [SELECT Id, Industry, Description FROM Account LIMIT 2]; + + Assert.areEqual('New Test Industry', accounts[0].Industry, 'Account industry should be "New Test Industry".'); + Assert.areEqual('New Test Description', accounts[0].Description, 'Account description should be "New Test Description".'); + + Assert.areEqual('New Test Industry', accounts[1].Industry, 'Account industry should be "New Test Industry".'); + Assert.areEqual('New Test Description', accounts[1].Description, 'Account description should be "New Test Description".'); + } + + @IsTest + static void retrieveResultForWhenIncorrectIdentifierIsUsed() { + // Setup + Exception expectedException = null; + + // Test + Test.startTest(); + try { + DML.retrieveResultFor('dmlMockId'); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('No result found for dml identifier: dmlMockId', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void mockAllDmls() { + // Setup + Account account1 = getAccount(1); + + Contact contact1 = getContact(1); + contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); + + Opportunity opportunity1 = getOpportunity(1); + opportunity1.Id = DML.randomIdGenerator.get(Opportunity.SObjectType); + + DML.mock('dmlMockId').allDmls(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toInsert(account1).toUpdate(contact1).toDelete(opportunity1).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Opportunity], 'No records should be deleted from the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); + Assert.areEqual(Opportunity.SObjectType, result.deletesOf(Opportunity.SObjectType).objectType(), 'Deleted operation result should contain Opportunity object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Opportunity.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + } + + @IsTest + static void resultAll() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toInsert(account).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, result.all().size(), 'Result should contain 1 operation result.'); + } + + @IsTest + static void resultInserts() { + // Setup + Account account1 = getAccount(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toInsert(account1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, result.inserts().size(), 'Result should contain 1 insert operation result.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + + DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.isFalse(operationResult.hasFailures(), 'Inserted operation result should not have failures.'); + } + + @IsTest + static void resultUpdates() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + account1.Name = 'Updated Test Account'; + + DML.Result result = new DML().toUpdate(account1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(1, result.updates().size(), 'Result should contain 1 update operation result.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); + Assert.isFalse(operationResult.hasFailures(), 'Updated operation result should not have failures.'); + } + + @IsTest + static void resultUpserts() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + account1.Name = 'Updated Test Account'; + + DML.Result result = new DML().toUpsert(account1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(1, result.upserts().size(), 'Result should contain 1 upsert operation result.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + + DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.isFalse(operationResult.hasFailures(), 'Upserted operation result should not have failures.'); + } + + @IsTest + static void resultDeletes() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toDelete(account1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(1, result.deletes().size(), 'Result should contain 1 delete operation result.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.isFalse(operationResult.hasFailures(), 'Deleted operation result should not have failures.'); + } + + @IsTest + static void resultUndeletes() { + // Setup + Account account1 = getAccount(1); + insert account1; + delete account1; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toUndelete(account1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(1, result.undeletes().size(), 'Result should contain 1 undelete operation result.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + + DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.isFalse(operationResult.hasFailures(), 'Undeleted operation result should not have failures.'); + } + + @IsTest + static void resultEvents() { + // Setup + FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + DML.Result result = new DML().toPublish(event1).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(1, result.events().size(), 'Result should contain 1 publish operation result.'); + + DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Published operation result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Published operation result should contain publish type.'); + } + + @IsTest + static void resultWhenFailure() { + // Setup + Account account1 = getAccount(1); + insert account1; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + account1.Name = ''; // Name is required + + new DML().toUpdate(account1).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Required fields are missing: [Name]'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void resultWhenFailureButAllowPartialSuccess() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + account1.Name = ''; // Name is required + + DML.Result result = new DML().toUpdate(account1).allowPartialSuccess().commitWork(); + Test.stopTest(); + + // Verify + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(0, operationResult.successes().size(), 'Updated operation result should contain 0 success record.'); + Assert.areEqual(1, operationResult.failures().size(), 'Updated operation result should contain 1 failure record.'); + + Assert.areEqual(1, operationResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(1, operationResult.errors().size(), 'Updated operation result should contain 1 error.'); + + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Updated operation result should contain update type.'); + Assert.isTrue(operationResult.hasFailures(), 'Updated operation result should have failures.'); + + DML.RecordResult recordResult = operationResult.recordResults()[0]; + + Assert.areEqual(account1.Id, recordResult.id(), 'Record result should contain the account id.'); + Assert.areEqual(account1, recordResult.record(), 'Record result should contain the account record.'); + Assert.isFalse(recordResult.isSuccess(), 'Record result should not be successful.'); + Assert.areEqual(1, recordResult.errors().size(), 'Record result should contain 1 error.'); + + DML.Error error = recordResult.errors()[0]; + + Assert.areEqual('Required fields are missing: [Name]', error.message(), 'Record result should contain the error message.'); + Assert.areEqual(System.StatusCode.REQUIRED_FIELD_MISSING, error.statusCode(), 'Record result should contain the error status code.'); + Assert.isTrue(error.fields().contains('Name'), 'Record result should contain the error fields.'); + } + + @IsTest + static void resultMerges() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + insert new List{ masterAccount, duplicateAccount }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().toMerge(masterAccount, duplicateAccount).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + Assert.areEqual(1, result.merges().size(), 'Result should contain 1 merge operation result.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Merged operation result should contain merge type.'); + Assert.isFalse(operationResult.hasFailures(), 'Merged operation result should not have failures.'); + } + + @IsTest + static void resultWhenNoOperations() { + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML().commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be executed.'); + Assert.areEqual(0, result.all().size(), 'Result should contain 0 operation results.'); + Assert.areEqual(0, result.inserts().size(), 'Result should contain 0 insert operation results.'); + Assert.areEqual(0, result.updates().size(), 'Result should contain 0 update operation results.'); + Assert.areEqual(0, result.upserts().size(), 'Result should contain 0 upsert operation results.'); + Assert.areEqual(0, result.deletes().size(), 'Result should contain 0 delete operation results.'); + Assert.areEqual(0, result.undeletes().size(), 'Result should contain 0 undelete operation results.'); + Assert.areEqual(0, result.merges().size(), 'Result should contain 0 merge operation results.'); + Assert.areEqual(0, result.events().size(), 'Result should contain 0 publish operation results.'); + } + + @IsTest + static void addInRandomOrder() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + Opportunity opportunity1 = getOpportunity(1); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + new DML() + .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)) + .toInsert(DML.Record(opportunity1).withRelationship(Opportunity.AccountId, account1)) + .toInsert(account1) + .commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue( + expectedException.getMessage().contains('Relationship was registered for a record that has not been registered yet.'), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void selfDependency() { + // Setup + Account account1 = getAccount(1); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + new DML().toInsert(account1).toInsert(DML.Record(account1).withRelationship(Account.ParentId, account1)).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Self-dependency detected for record: ' + account1.Id), 'Expected exception message should be thrown.'); + } + + @IsTest + static void cyclicDependency() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + new DML() + .toInsert(account1) + .toInsert(DML.Record(account2).withRelationship(Account.ParentId, account1)) + .toInsert(DML.Record(account1).withRelationship(Account.ParentId, account2)) + .commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Cyclic dependencies detected among records.'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void registerComplex() { + // Setup + DML uow = new DML(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + for (Integer i = 0; i < 10; i++) { + Opportunity newOpportunity = new Opportunity(Name = 'UoW Test Name ' + i, StageName = 'Open', CloseDate = System.today()); + + uow.toInsert(newOpportunity); + + for (Integer j = 0; j < i + 1; j++) { + Product2 product = new Product2(Name = newOpportunity.Name + ' : Product : ' + i); + + uow.toInsert(product); + + PricebookEntry pbe = new PricebookEntry(UnitPrice = 10, IsActive = true, UseStandardPrice = false, Pricebook2Id = Test.getStandardPricebookId()); + + uow.toInsert(DML.Record(pbe).withRelationship(PricebookEntry.Product2Id, product)); + + uow.toInsert( + DML.Record(new OpportunityLineItem(Quantity = 1, TotalPrice = 10)) + .withRelationship(OpportunityLineItem.PricebookEntryId, pbe) + .withRelationship(OpportunityLineItem.OpportunityId, newOpportunity) + ); + } + } + uow.commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(4, dmlsAfter - dmlsBefore, '4 DML statements should be executed (Opportunity, Product2, PricebookEntry, OpportunityLineItem).'); + Assert.areEqual(10, [SELECT COUNT() FROM Opportunity], 'Opportunities should be inserted.'); + Assert.areEqual(55, [SELECT COUNT() FROM Product2], 'Products should be inserted.'); + Assert.areEqual(55, [SELECT COUNT() FROM PricebookEntry], 'Pricebook entries should be inserted.'); + } + + @IsTest + static void insertUpdateAndDeleteWithRelationships() { + // Setup + Account existingAccount = getAccount(1); + insert existingAccount; + + Account newAccount = getAccount(2); + Contact contact1 = getContact(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML() + .toInsert(newAccount) + .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, newAccount)) + .toUpdate(DML.Record(existingAccount).with(Account.Description, 'Marked for review')) + .toDelete(existingAccount) + .commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(4, dmlsAfter - dmlsBefore, '4 DML statements should be executed (Account insert, Contact insert, Account update, Account delete).'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only the new account should exist.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted.'); + Assert.areEqual(newAccount.Id, [SELECT AccountId FROM Contact WHERE Id = :contact1.Id].AccountId, 'Contact should be linked to the new Account.'); + Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 2 object types.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 object type.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 object type.'); + } + + @IsTest + static void multiLevelRelationshipHierarchy() { + // Setup + Account grandParentAccount = new Account(Name = 'Grand Parent Account'); + Account parentAccount = new Account(Name = 'Parent Account'); + Account childAccount = new Account(Name = 'Child Account'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML() + .toInsert(grandParentAccount) + .toInsert(DML.Record(parentAccount).withRelationship(Account.ParentId, grandParentAccount)) + .toInsert(DML.Record(childAccount).withRelationship(Account.ParentId, parentAccount)) + .commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed (one per hierarchy level due to dependencies).'); + Assert.areEqual(3, [SELECT COUNT() FROM Account], 'All accounts should be inserted.'); + + Account queriedChild = [SELECT ParentId, Parent.ParentId FROM Account WHERE Id = :childAccount.Id]; + Assert.areEqual(parentAccount.Id, queriedChild.ParentId, 'Child should be linked to parent.'); + Assert.areEqual(grandParentAccount.Id, queriedChild.Parent.ParentId, 'Parent should be linked to grandparent.'); + } + + @IsTest + static void multipleDependentObjectTypesWithRelationships() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + Opportunity opportunity1 = getOpportunity(1); + Case case1 = new Case(Subject = 'Test Case 1', Status = 'New'); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Result result = new DML() + .toInsert(account1) + .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)) + .toInsert(DML.Record(opportunity1).withRelationship(Opportunity.AccountId, account1)) + .toInsert(DML.Record(case1).withRelationship(Case.AccountId, account1).withRelationship(Case.ContactId, contact1)) + .commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(4, dmlsAfter - dmlsBefore, '4 DML statements should be executed (Account, Contact, Opportunity, Case).'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Opportunity], 'Opportunity should be inserted.'); + Assert.areEqual(1, [SELECT COUNT() FROM Case], 'Case should be inserted.'); + + Case queriedCase = [SELECT AccountId, ContactId FROM Case WHERE Id = :case1.Id]; + Assert.areEqual(account1.Id, queriedCase.AccountId, 'Case should be linked to Account.'); + Assert.areEqual(contact1.Id, queriedCase.ContactId, 'Case should be linked to Contact.'); + Assert.areEqual(4, result.inserts().size(), 'Inserted operation result should contain 4 object types.'); + } + + @IsTest + static void insertWithRelationshipsAndImmediateOperations() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + Contact contact2 = getContact(2); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML db = new DML(); + + // Insert account immediately + db.insertImmediately(account1); + + // Register contact with relationship to the already-inserted account + db.toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)).toInsert(DML.Record(contact2).withRelationship(Contact.AccountId, account1)).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed (Account immediate, Contacts commit).'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.areEqual(2, [SELECT COUNT() FROM Contact], 'Contacts should be inserted.'); + Assert.areEqual(account1.Id, [SELECT AccountId FROM Contact WHERE Id = :contact1.Id].AccountId, 'Contact 1 should be linked to Account.'); + Assert.areEqual(account1.Id, [SELECT AccountId FROM Contact WHERE Id = :contact2.Id].AccountId, 'Contact 2 should be linked to Account.'); + } + + @IsTest + static void upsertAndInsertWithRelationships() { + // Setup + Account existingAccount = getAccount(1); + insert existingAccount; + + Account newAccount = getAccount(2); + Contact contact1 = getContact(1); + Contact contact2 = getContact(2); + + existingAccount.Description = 'Updated Description'; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML() + .toUpsert(existingAccount) // Update existing + .toInsert(newAccount) // Insert new + .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, existingAccount)) + .toInsert(DML.Record(contact2).withRelationship(Contact.AccountId, newAccount)) + .commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed (Account upsert, Account insert, Contact insert).'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Both accounts should exist.'); + Assert.areEqual(2, [SELECT COUNT() FROM Contact], 'Contacts should be inserted.'); + Assert.areEqual('Updated Description', [SELECT Description FROM Account WHERE Id = :existingAccount.Id].Description, 'Existing account should be updated.'); + Assert.areEqual(existingAccount.Id, [SELECT AccountId FROM Contact WHERE Id = :contact1.Id].AccountId, 'Contact 1 should be linked to existing Account.'); + Assert.areEqual(newAccount.Id, [SELECT AccountId FROM Contact WHERE Id = :contact2.Id].AccountId, 'Contact 2 should be linked to new Account.'); + } + + @IsTest + static void complexHierarchyWithMocking() { + // Setup - 3 level hierarchy with multiple branches + Account parentAccount = new Account(Name = 'Parent'); + Account childAccount1 = new Account(Name = 'Child 1'); + Account childAccount2 = new Account(Name = 'Child 2'); + Contact contact1 = getContact(1); + Contact contact2 = getContact(2); + Opportunity opportunity1 = getOpportunity(1); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML() + .identifier('dmlMockId') + .toInsert(parentAccount) + .toInsert(DML.Record(childAccount1).withRelationship(Account.ParentId, parentAccount)) + .toInsert(DML.Record(childAccount2).withRelationship(Account.ParentId, parentAccount)) + .toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, childAccount1)) + .toInsert(DML.Record(contact2).withRelationship(Contact.AccountId, childAccount2)) + .toInsert(DML.Record(opportunity1).withRelationship(Opportunity.AccountId, parentAccount)) + .commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(3, result.inserts().size(), 'Inserted operation result should contain 3 object types (Account, Contact, Opportunity).'); + Assert.areEqual(3, result.insertsOf(Account.SObjectType).records().size(), 'Account result should contain 3 records.'); + Assert.areEqual(2, result.insertsOf(Contact.SObjectType).records().size(), 'Contact result should contain 2 records.'); + Assert.areEqual(1, result.insertsOf(Opportunity.SObjectType).records().size(), 'Opportunity result should contain 1 record.'); + } + + @IsTest + static void toInsertSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + } + + @IsTest + static void toInsertSingleRecordWithMockingAndRelationship() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).toInsert(DML.Record(contact1).withRelationship(Contact.AccountId, account1)).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + + DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, accountResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult contactResult = result.insertsOf(Contact.SObjectType); + + Assert.areEqual(Contact.SObjectType, contactResult.objectType(), 'Inserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, contactResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, contactResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, contactResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(contactResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(contactResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); + } + + @IsTest + static void toInsertMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).toInsert(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(2, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted records.'); + Assert.areEqual(2, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[1].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + } + + @IsTest + static void toInsertMultipleRecordsWithMockingSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + DML.mock('dmlMockId').insertsFor(Account.SObjectType); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).toInsert(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed for contact which is not mocked.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be inserted, because it was mocked.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be inserted, because only Account was mocked.'); + Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.insertsOf(Contact.SObjectType).objectType(), 'Inserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Contact.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(1, result.insertsOf(Contact.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.insertsOf(Contact.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isTrue(result.insertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.insertsOf(Contact.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); + } + + @IsTest + static void toInsertMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(account1).toInsert(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(2, result.inserts().size(), 'Inserted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.insertsOf(Account.SObjectType).objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.insertsOf(Contact.SObjectType).objectType(), 'Inserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Account.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, result.insertsOf(Contact.SObjectType).operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.insertsOf(Account.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.areEqual(1, result.insertsOf(Contact.SObjectType).records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, result.insertsOf(Contact.SObjectType).recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isTrue(result.insertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(result.insertsOf(Account.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.insertsOf(Contact.SObjectType).recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + Assert.isNotNull(contact1.Id, 'Contact should have mocked Id.'); + } + + @IsTest + static void toInsertWithEmptyRecordWhenMocking() { + // Setup + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toInsert(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toInsertWithMockingException() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnInserts(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toInsertWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnInserts(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toInsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Result should contain insert type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toInsertWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnInsertsFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toInsertWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnInsertsFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toInsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, operationResult.operationType(), 'Result should contain insert type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpdateSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account'; + + new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual('Updated Test Account', account1.Name, 'Account should be updated.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUpdateMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account 1'; + account2.Name = 'Updated Test Account 2'; + + new DML().toUpdate(account1).toUpdate(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(2, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated records.'); + Assert.areEqual(2, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[1].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Account 2', account2.Name, 'Account 2 should be updated.'); + } + + @IsTest + static void toUpdateMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); + + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account 1'; + contact1.FirstName = 'Updated Test Contact 1'; + + new DML().toUpdate(account1).toUpdate(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(2, result.updates().size(), 'Updated operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(1, result.updatesOf(Contact.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.updatesOf(Contact.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isTrue(result.updatesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.isNotNull(result.updatesOf(Contact.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.areEqual('Updated Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Updated Test Contact 1', contact1.FirstName, 'Contact 1 should be updated.'); + } + + @IsTest + static void toUpdateMultipleRecordsWithMockingSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + insert account1; + + Contact contact1 = getContact(1); + insert contact1; + + DML.mock('dmlMockId').updatesFor(Account.SObjectType); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Test Account 1'; + contact1.FirstName = 'Updated Test Contact 1'; + + new DML().toUpdate(account1).toUpdate(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should not be updated in the database.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should not be updated in the database.'); + Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); + Assert.areEqual(2, result.updates().size(), 'Updated operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.updatesOf(Account.SObjectType).objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.updatesOf(Contact.SObjectType).objectType(), 'Updated operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Account.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, result.updatesOf(Contact.SObjectType).operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.updatesOf(Account.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.areEqual(1, result.updatesOf(Contact.SObjectType).records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, result.updatesOf(Contact.SObjectType).recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(result.updatesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isTrue(result.updatesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(result.updatesOf(Account.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.isNotNull(result.updatesOf(Contact.SObjectType).recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUpdateWithMockingException() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUpdates(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUpdateWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUpdates(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUpdate(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Result should contain update type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpdateWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUpdatesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpdate(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUpdateWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUpdatesFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUpdate(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, operationResult.operationType(), 'Result should contain update type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpdateWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toUpdate(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 1 result.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toUpsertSingleRecordWhenIdIsNotSpecifiedWithMocking() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account'; + + new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + } + + @IsTest + static void toUpsertSingleRecordWhenIdIsSpecifiedWithMocking() { + // Setup + Account account1 = getAccount(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account'; + + new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual('Upserted Test Account', account1.Name, 'Account should be upserted.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + } + + @IsTest + static void toUpsertGenericSObjectListWithExternalIdFieldWithMocking() { + // Setup + Account account1 = getAccount(1); + List genericRecords = new List{ account1 }; + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUpsert(genericRecords, Account.Id).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + } + + @IsTest + static void listSObjectOverloadsResolveRuntimeTypeAcrossOperationsWithMocking() { + // Setup + Account insertAccount = getAccount(101); + List insertRecords = new List{ insertAccount }; + DML.mock('genericInsertMockId').allInserts(); + + Account updateAccount = getAccount(102); + updateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List updateRecords = new List{ updateAccount }; + DML.mock('genericUpdateMockId').allUpdates(); + + Account deleteAccount = getAccount(103); + deleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List deleteRecords = new List{ deleteAccount }; + DML.mock('genericDeleteMockId').allDeletes(); + + Account hardDeleteAccount = getAccount(104); + hardDeleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List hardDeleteRecords = new List{ hardDeleteAccount }; + DML.mock('genericHardDeleteMockId').allDeletes(); + + Account undeleteAccount = getAccount(105); + undeleteAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + List undeleteRecords = new List{ undeleteAccount }; + DML.mock('genericUndeleteMockId').allUndeletes(); + + // Test + Test.startTest(); + new DML().toInsert(insertRecords).identifier('genericInsertMockId').commitWork(); + new DML().toUpdate(updateRecords).identifier('genericUpdateMockId').commitWork(); + new DML().toDelete(deleteRecords).identifier('genericDeleteMockId').commitWork(); + new DML().toHardDelete(hardDeleteRecords).identifier('genericHardDeleteMockId').commitWork(); + new DML().toUndelete(undeleteRecords).identifier('genericUndeleteMockId').commitWork(); + Test.stopTest(); + + // Verify + Assert.areEqual( + 1, + DML.retrieveResultFor('genericInsertMockId').insertsOf(Account.SObjectType).recordResults().size(), + 'Insert result should be keyed by Account SObjectType.' + ); + Assert.areEqual( + 1, + DML.retrieveResultFor('genericUpdateMockId').updatesOf(Account.SObjectType).recordResults().size(), + 'Update result should be keyed by Account SObjectType.' + ); + Assert.areEqual( + 1, + DML.retrieveResultFor('genericDeleteMockId').deletesOf(Account.SObjectType).recordResults().size(), + 'Delete result should be keyed by Account SObjectType.' + ); + Assert.areEqual( + 1, + DML.retrieveResultFor('genericHardDeleteMockId').deletesOf(Account.SObjectType).recordResults().size(), + 'Hard delete result should be keyed by Account SObjectType.' + ); + Assert.areEqual( + 1, + DML.retrieveResultFor('genericUndeleteMockId').undeletesOf(Account.SObjectType).recordResults().size(), + 'Undelete result should be keyed by Account SObjectType.' + ); + } + + @IsTest + static void listSObjectOverloadsThrowWhenMixedTypesProvided() { + // Setup + List mixedRecords = new List{ getAccount(1), getContact(1) }; + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpsert(mixedRecords).identifier('mixedSObjectTypeMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown for mixed SObject types in one list operation.'); + Assert.isTrue(expectedException.getMessage().contains('Mixed SObject types'), 'Expected mixed SObject type validation message.'); + } + + @IsTest + static void toUpsertMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account 1'; + account2.Name = 'Upserted Test Account 2'; + + new DML().toUpsert(account1).toUpsert(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(2, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(2, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[1].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be upserted.'); + Assert.areEqual('Upserted Test Account 2', account2.Name, 'Account 2 should be upserted.'); + } + + @IsTest + static void toUpsertMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account 1'; + contact1.FirstName = 'Upserted Test Contact 1'; + + new DML().toUpsert(account1).toUpsert(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No records should be inserted to the database.'); + Assert.areEqual(2, result.upserts().size(), 'Upserted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.upsertsOf(Contact.SObjectType).objectType(), 'Upserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Contact.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isTrue(result.upsertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.upsertsOf(Contact.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.areEqual('Upserted Test Account 1', account1.Name, 'Account 1 should be updated.'); + Assert.areEqual('Upserted Test Contact 1', contact1.FirstName, 'Contact 1 should be updated.'); + } + + @IsTest + static void toUpsertMultipleRecordsWithMockingSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + insert account1; + + Contact contact1 = getContact(1); + + DML.mock('dmlMockId').upsertsFor(Contact.SObjectType); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + account1.Name = 'Upserted Test Account 1'; + contact1.FirstName = 'Upserted Test Contact 1'; + + new DML().toUpsert(account1).toUpsert(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should not be updated in the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'Contact should be upserted in the database.'); + Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); + Assert.areEqual(2, result.upserts().size(), 'Upserted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.upsertsOf(Account.SObjectType).objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.upsertsOf(Contact.SObjectType).objectType(), 'Upserted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Account.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, result.upsertsOf(Contact.SObjectType).operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, result.upsertsOf(Account.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, result.upsertsOf(Contact.SObjectType).recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(result.upsertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isTrue(result.upsertsOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(result.upsertsOf(Account.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.upsertsOf(Contact.SObjectType).recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUpsertWithMockingException() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnUpserts(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUpsertWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnUpserts(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUpsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Result should contain upsert type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpsertWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnUpsertsFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpsert(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUpsertWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').exceptionOnUpsertsFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUpsert(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, operationResult.operationType(), 'Result should contain upsert type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUpsertWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toUpsert(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toDeleteSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toDelete(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteSingleRecordByIdWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toDelete(account1.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toDelete(account1).toDelete(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteMultipleRecordsByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toDelete(new List{ account1.Id, account2.Id }).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toDelete(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toDeleteMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toDelete(account1).toDelete(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should exist, because delete was mocked.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No Contact records should exist, because delete was mocked.'); + Assert.areEqual(2, result.deletes().size(), 'Deleted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.deletesOf(Contact.SObjectType).objectType(), 'Deleted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Contact.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(1, result.deletesOf(Contact.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Contact.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(result.deletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.deletesOf(Contact.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteMultipleRecordsWithMockingSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + insert account1; + + Contact contact1 = getContact(1); + insert contact1; + + DML.mock('dmlMockId').deletesFor(Account.SObjectType); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toDelete(account1).toDelete(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should not be deleted from the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'Contact should be deleted from the database.'); + Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); + Assert.areEqual(2, result.deletes().size(), 'Deleted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.deletesOf(Contact.SObjectType).objectType(), 'Deleted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Contact.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.areEqual(1, result.deletesOf(Contact.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Contact.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(result.deletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.deletesOf(Contact.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteWithMockingException() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletes(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toDelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toDeleteWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletes(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toDelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Result should contain delete type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toDeleteWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toDelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toDeleteWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toDelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, operationResult.operationType(), 'Result should contain delete type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toHardDeleteSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toHardDeleteMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toHardDelete(account1).toHardDelete(account2).identifier('dmlMockId').commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, result.deletesOf(Account.SObjectType).operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, result.deletesOf(Account.SObjectType).records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, result.deletesOf(Account.SObjectType).recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(result.deletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.deletesOf(Account.SObjectType).recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toHardDeleteByIdWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toHardDelete(account1.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toHardDeleteByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toHardDelete(new Set{ account1.Id, account2.Id }).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be hard deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toHardDeleteWithMockingException() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletes(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toHardDeleteWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnDeletesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toHardDelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toHardDeleteWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toHardDelete(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toUndeleteSingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.deletesOf(Account.SObjectType).objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUndelete(account1).toUndelete(account2).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(2, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteByIdWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toUndelete(account1.Id).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').toUndelete(new List{ account1.Id, account2.Id }).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteMultipleRecordTypesWithMocking() { + // Setup + Account account1 = getAccount(1); + Contact contact1 = getContact(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + contact1.Id = DML.randomIdGenerator.get(Contact.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toUndelete(account1).toUndelete(contact1).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should exist, because undelete was mocked.'); + Assert.areEqual(0, [SELECT COUNT() FROM Contact], 'No Contact records should exist, because undelete was mocked.'); + Assert.areEqual(2, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.undeletesOf(Contact.SObjectType).objectType(), 'Undeleted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Contact.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(result.undeletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.undeletesOf(Contact.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteMultipleRecordsWithMockingSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + insert account1; + delete account1; + + Contact contact1 = getContact(1); + insert contact1; + delete contact1; + + DML.mock('dmlMockId').undeletesFor(Account.SObjectType); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toUndelete(account1).toUndelete(contact1).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Account should not be undeleted in the database.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'Contact should be undeleted in the database.'); + Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); + Assert.areEqual(2, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.undeletesOf(Account.SObjectType).objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(Contact.SObjectType, result.undeletesOf(Contact.SObjectType).objectType(), 'Undeleted operation result should contain Contact object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Account.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, result.undeletesOf(Contact.SObjectType).operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.undeletesOf(Account.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, result.undeletesOf(Contact.SObjectType).recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(result.undeletesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(result.undeletesOf(Contact.SObjectType).recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(result.undeletesOf(Account.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(result.undeletesOf(Contact.SObjectType).recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteWithMockingException() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUndeletes(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUndeleteWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUndeletes(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUndelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Result should contain undelete type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUndeleteWithMockingExceptionForSpecificSObjectType() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUndeletesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUndelete(account1).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toUndeleteWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnUndeletesFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toUndelete(account1).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, operationResult.operationType(), 'Result should contain undelete type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toUndeleteWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toUndelete(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted in the database.'); + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); + } + + @IsTest + static void toMergeSingleRecordWithMocking() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allMerges(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be in the database.'); + Assert.areEqual(1, result.merges().size(), 'Merged operation result should contain 1 result.'); + Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Account.SObjectType).operationType(), 'Merged operation result should contain merge type.'); + Assert.areEqual(1, result.mergesOf(Account.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, result.mergesOf(Account.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.isTrue(result.mergesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); + Assert.isNotNull(result.mergesOf(Account.SObjectType).recordResults()[0].id(), 'Merged operation result should contain a mocked record Id.'); + } + + @IsTest + static void toMergeMultipleRecordTypesWithMocking() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + Lead masterLead = getLead(1); + Lead duplicateLead = getLead(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + masterLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); + duplicateLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); + + DML.mock('dmlMockId').allMerges(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toMerge(masterAccount, duplicateAccount).toMerge(masterLead, duplicateLead).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No Account records should be in the database.'); + Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'No Lead records should be in the database.'); + Assert.areEqual(2, result.merges().size(), 'Merged operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(Lead.SObjectType, result.mergesOf(Lead.SObjectType).objectType(), 'Merged operation result should contain Lead object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Account.SObjectType).operationType(), 'Merged operation result should contain merge type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, result.mergesOf(Lead.SObjectType).operationType(), 'Merged operation result should contain merge type.'); + Assert.areEqual(1, result.mergesOf(Account.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, result.mergesOf(Account.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.areEqual(1, result.mergesOf(Lead.SObjectType).records().size(), 'Merged operation result should contain the merged record.'); + Assert.areEqual(1, result.mergesOf(Lead.SObjectType).recordResults().size(), 'Merged operation result should contain the merged record results.'); + Assert.isTrue(result.mergesOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); + Assert.isTrue(result.mergesOf(Lead.SObjectType).recordResults()[0].isSuccess(), 'Merged operation result should contain a successful record result.'); + } + + @IsTest + static void toMergeWithMockingSpecificSObjectType() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + insert new List{ masterAccount, duplicateAccount }; + + Lead masterLead = getLead(1); + Lead duplicateLead = getLead(2); + + masterLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); + duplicateLead.Id = DML.randomIdGenerator.get(Lead.SObjectType); + + DML.mock('dmlMockId').mergesFor(Lead.SObjectType); + + // Test + Test.startTest(); + new DML().toMerge(masterAccount, duplicateAccount).toMerge(masterLead, duplicateLead).identifier('dmlMockId').commitWork(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account merge should not be mocked.'); + Assert.areEqual(0, [SELECT COUNT() FROM Lead], 'Lead merge should be mocked.'); + Assert.areEqual(2, result.merges().size(), 'Merged operation result should contain 2 results.'); + Assert.areEqual(Account.SObjectType, result.mergesOf(Account.SObjectType).objectType(), 'Merged operation result should contain Account object type.'); + Assert.areEqual(Lead.SObjectType, result.mergesOf(Lead.SObjectType).objectType(), 'Merged operation result should contain Lead object type.'); + } + + @IsTest + static void toMergeWithMockingException() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnMerges(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toMergeWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnMerges(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toMerge(masterAccount, duplicateAccount).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Result should contain merge type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toMergeWithMockingExceptionForSpecificSObjectType() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnMergesFor(Account.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toMerge(masterAccount, duplicateAccount).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toMergeWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + duplicateAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').exceptionOnMergesFor(Account.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toMerge(masterAccount, duplicateAccount).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.mergesOf(Account.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(Account.SObjectType, operationResult.objectType(), 'Result should contain Account object type.'); + Assert.areEqual(DML.OperationType.MERGE_DML, operationResult.operationType(), 'Result should contain merge type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toMergeWithEmptyRecordsWhenMocking() { + // Setup + Account masterAccount = getAccount(1); + masterAccount.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allMerges(); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toMerge(masterAccount, new List()).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify); + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + } + + @IsTest + static void toPublishSingleRecordWithMocking() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toPublish(event).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void toPublishSingleRecordWithMockingSpecificSObjectType() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').publishesFor(FlowOrchestrationEvent.SObjectType); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toPublish(event).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void toPublishMultipleRecordsWithMocking() { + // Setup + FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); + FlowOrchestrationEvent event2 = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toPublish(event1).toPublish(event2).identifier('dmlMockId').commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published records.'); + Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void toPublishWithMockingException() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').exceptionOnPublishes(); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toPublish(event).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toPublishWithMockingExceptionWhenAllOrNoneIsSet() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').exceptionOnPublishes(); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toPublish(event).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Result should contain publish type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toPublishWithMockingExceptionForSpecificSObjectType() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').exceptionOnPublishesFor(FlowOrchestrationEvent.SObjectType); + + Exception expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toPublish(event).identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toPublishWithMockingExceptionForSpecificSObjectTypeWhenAllOrNoneIsSet() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').exceptionOnPublishesFor(FlowOrchestrationEvent.SObjectType); + + Exception expectedException = null; + DML.Result result = null; + + // Test + Test.startTest(); + try { + result = new DML().toPublish(event).allowPartialSuccess().identifier('dmlMockId').commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNull(expectedException, 'Expected exception to not be thrown.'); + + DML.OperationResult operationResult = result.eventsOf(FlowOrchestrationEvent.SObjectType); + + Assert.areEqual(1, operationResult.records().size(), 'Result should contain 1 record.'); + Assert.areEqual(1, operationResult.recordResults().size(), 'Result should contain 1 record result.'); + Assert.areEqual(FlowOrchestrationEvent.SObjectType, operationResult.objectType(), 'Result should contain FlowOrchestrationEvent object type.'); + Assert.areEqual(DML.OperationType.PUBLISH_DML, operationResult.operationType(), 'Result should contain publish type.'); + Assert.isFalse(operationResult.recordResults()[0].isSuccess(), 'Result should contain a failed record result.'); + Assert.isTrue(operationResult.hasFailures(), 'Result should have failures.'); + } + + @IsTest + static void toPublishWithEmptyRecordsWhenMocking() { + // Setup + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + new DML().toPublish(new List()).identifier('dmlMockId').commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlStatementsAfter - dmlStatementsBefore, 'No DML statements should be executed.'); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 1 result.'); + } + + @IsTest + static void toInsertImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').insertImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(1, accountResult.records().size(), 'Inserted operation result should contain the inserted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toInsertImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').insertImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.insertsOf(Account.SObjectType); + + Assert.areEqual(DML.OperationType.INSERT_DML, accountResult.operationType(), 'Inserted operation result should contain insert type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Inserted operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Inserted operation result should contain the inserted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Inserted operation result should contain the inserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Inserted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUpsertImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').upsertImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted to the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPSERT_DML, accountResult.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(1, accountResult.records().size(), 'Upserted operation result should contain the upserted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUpsertImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + DML.mock('dmlMockId').allUpserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').upsertImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be upserted to the database.'); + Assert.areEqual(1, result.upserts().size(), 'Upserted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.upsertsOf(Account.SObjectType); + + Assert.areEqual(DML.OperationType.UPSERT_DML, accountResult.operationType(), 'Upserted operation result should contain upsert type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Upserted operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Upserted operation result should contain the upserted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Upserted operation result should contain the upserted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Upserted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Upserted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Upserted operation result should contain a mocked record Id.'); + } + + // MOCKING - UPDATE IMMEDIATELY + + @IsTest + static void toUpdateImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').updateImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated to the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UPDATE_DML, accountResult.operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(1, accountResult.records().size(), 'Updated operation result should contain the updated record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUpdateImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUpdates(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').updateImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be updated to the database.'); + Assert.areEqual(1, result.updates().size(), 'Updated operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.updatesOf(Account.SObjectType); + + Assert.areEqual(DML.OperationType.UPDATE_DML, accountResult.operationType(), 'Updated operation result should contain update type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Updated operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Updated operation result should contain the updated records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Updated operation result should contain the updated record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Updated operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Updated operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Updated operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').deleteImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteImmediatelyByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').deleteImmediately(account1.Id); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Deleted operation result should contain the deleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').deleteImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toDeleteImmediatelyMultipleRecordsByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allDeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').deleteImmediately(new List{ account1.Id, account2.Id }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be deleted from the database.'); + Assert.areEqual(1, result.deletes().size(), 'Deleted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.deletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Deleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.DELETE_DML, accountResult.operationType(), 'Deleted operation result should contain delete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Deleted operation result should contain the deleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Deleted operation result should contain the deleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Deleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Deleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Deleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteImmediatelySingleRecordWithMocking() { + // Setup + Account account1 = getAccount(1); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').undeleteImmediately(account1); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void toUndeleteImmediatelyMultipleRecordsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').undeleteImmediately(new List{ account1, account2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); + Assert.areEqual(1, result.undeletes().size(), 'Undeleted operation result should contain 2 results.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void undeleteImmediatelyByIdWithMocking() { + // Setup + Account account1 = getAccount(1); + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').undeleteImmediately(account1.Id); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); + Assert.isNotNull(account1.Id, 'Account should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(1, accountResult.records().size(), 'Undeleted operation result should contain the undeleted record.'); + Assert.areEqual(1, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void undeleteImmediatelyByIdsWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + account1.Id = DML.randomIdGenerator.get(Account.SObjectType); + account2.Id = DML.randomIdGenerator.get(Account.SObjectType); + + DML.mock('dmlMockId').allUndeletes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').undeleteImmediately(new List{ account1.Id, account2.Id }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be undeleted from the database.'); + Assert.isNotNull(account1.Id, 'Account 1 should have mocked Id.'); + Assert.isNotNull(account2.Id, 'Account 2 should have mocked Id.'); + + DML.OperationResult accountResult = result.undeletesOf(Account.SObjectType); + + Assert.areEqual(Account.SObjectType, accountResult.objectType(), 'Undeleted operation result should contain Account object type.'); + Assert.areEqual(DML.OperationType.UNDELETE_DML, accountResult.operationType(), 'Undeleted operation result should contain undelete type.'); + Assert.areEqual(2, accountResult.records().size(), 'Undeleted operation result should contain the undeleted records.'); + Assert.areEqual(2, accountResult.recordResults().size(), 'Undeleted operation result should contain the undeleted record results.'); + Assert.isTrue(accountResult.recordResults()[0].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isTrue(accountResult.recordResults()[1].isSuccess(), 'Undeleted operation result should contain a successful record result.'); + Assert.isNotNull(accountResult.recordResults()[0].id(), 'Undeleted operation result should contain a mocked record Id.'); + Assert.isNotNull(accountResult.recordResults()[1].id(), 'Undeleted operation result should contain a mocked record Id.'); + } + + @IsTest + static void publishImmediatelySingleRecordWithMocking() { + // Setup + FlowOrchestrationEvent event = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').publishImmediately(event); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published record.'); + Assert.areEqual(1, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void publishImmediatelyMultipleRecordsWithMocking() { + // Setup + FlowOrchestrationEvent event1 = new FlowOrchestrationEvent(); + FlowOrchestrationEvent event2 = new FlowOrchestrationEvent(); + + DML.mock('dmlMockId').allPublishes(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().identifier('dmlMockId').publishImmediately(new List{ event1, event2 }); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(1, result.events().size(), 'Published operation result should contain 1 result.'); + Assert.areEqual( + FlowOrchestrationEvent.SObjectType, + result.eventsOf(FlowOrchestrationEvent.SObjectType).objectType(), + 'Published operation result should contain FlowOrchestrationEvent object type.' + ); + Assert.areEqual( + DML.OperationType.PUBLISH_DML, + result.eventsOf(FlowOrchestrationEvent.SObjectType).operationType(), + 'Published operation result should contain publish type.' + ); + Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).records().size(), 'Published operation result should contain the published records.'); + Assert.areEqual(2, result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults().size(), 'Published operation result should contain the published record results.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isTrue(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].isSuccess(), 'Published operation result should contain a successful record result.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[0].id(), 'Published operation result should contain a mocked record Id.'); + Assert.isNotNull(result.eventsOf(FlowOrchestrationEvent.SObjectType).recordResults()[1].id(), 'Published operation result should contain a mocked record Id.'); + } + + @IsTest + static void retrieveResultForWhichDoesNotExist() { + // Setup + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + DML.retrieveResultFor('dmlMockId'); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('No result found for dml identifier: dmlMockId'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void sharedDmlInstance() { + Account account = getAccount(1); + + Test.startTest(); + Integer dmlStatementsBefore = Limits.getDMLStatements(); + + DML.Shared.toInsert(account).commitWork(); + DML.Shared.commitWork(); + + Integer dmlStatementsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Account should be inserted.'); + Assert.areEqual(1, dmlStatementsAfter - dmlStatementsBefore, 'DML statements should be 1, because second commitWork() should not do anything.'); + } + + @IsTest + static void sharedDmlInstanceWithMocking() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + DML.mock('dmlMockId').allInserts(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Shared.identifier('dmlMockId').toInsert(account1).toInsert(account2).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + DML.Result result = DML.retrieveResultFor('dmlMockId'); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the mocking.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No records should be inserted to the database.'); + Assert.areEqual(1, result.inserts().size(), 'Inserted operation result should contain 1 result.'); + Assert.areEqual(2, result.insertsOf(Account.SObjectType).records().size(), 'Inserted operation result should contain 2 records.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[0].isSuccess(), 'Inserted operation result should contain a successful record result.'); + Assert.isTrue(result.insertsOf(Account.SObjectType).recordResults()[1].isSuccess(), 'Inserted operation result should contain a successful record result.'); + } + + @IsTest + static void sharedDmlInstanceMultipleOperations() { + // Setup + Account account1 = getAccount(1); + insert account1; + + Account account2 = getAccount(2); + Contact contact1 = getContact(1); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + account1.Name = 'Updated Account'; + + DML.Shared.toInsert(account2).toUpdate(account1).toInsert(contact1).commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(3, dmlsAfter - dmlsBefore, '3 DML statements should be executed (2 inserts + 1 update).'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Two accounts should exist.'); + Assert.areEqual(1, [SELECT COUNT() FROM Contact], 'One contact should exist.'); + Assert.areEqual('Updated Account', [SELECT Name FROM Account WHERE Id = :account1.Id].Name, 'Account 1 should be updated.'); + } + + @IsTest + static void sharedDmlInstanceDiscardWork() { + // Setup + Account account1 = getAccount(1); + Account account2 = getAccount(2); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + DML.Shared.toInsert(account1).toInsert(account2).discardWork(); + DML.Shared.commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(0, dmlsAfter - dmlsBefore, 'No DML statements should be executed, because work was discarded.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'No accounts should be inserted, because work was discarded.'); + Assert.isNull(account1.Id, 'Account 1 should not have an Id.'); + Assert.isNull(account2.Id, 'Account 2 should not have an Id.'); + } + + @IsTest + static void allowFieldTruncationOption() { + // Setup + String longAccountName = 'Test Account ' + 'Test'.repeat(' ', 100); + Account account = getAccount(1); + account.Name = longAccountName; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + Database.DmlOptions options = new Database.DmlOptions(); + options.allowFieldTruncation = true; + + new DML().toInsert(account).options(options).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + List accounts = getAccounts(); + + Assert.areEqual(1, accounts.size(), 'Account should be inserted.'); + Assert.areEqual(255, accounts[0].Name.length(), 'Account name should be 255 characters long, because allowFieldTruncation is true.'); + } + + @IsTest + static void optionsAllOrNoneDisabled() { + // Setup + List accounts = new List{ + getAccount(1), + new Account() // Name is required + }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + Database.DmlOptions options = new Database.DmlOptions(); + options.optAllOrNone = false; + + new DML().toInsert(accounts).options(options).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only one account should be inserted, because one has missing required field.'); + } + + @IsTest + static void skipDuplicateRules() { + // Setup + Account account = getAccount(1); + Account duplicateAccount = new Account(Name = account.Name); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toInsert(account).toInsert(duplicateAccount).skipDuplicateRules().commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(2, [SELECT COUNT() FROM Account], 'Both accounts should be inserted, because skipDuplicateRules is true.'); + } + + @IsTest + static void optionsAllOrNoneEnabled() { + // Setup + List accounts = new List{ + getAccount(1), + new Account() // Name is required + }; + + Exception expectedException = null; + + // Test + Test.startTest(); + Database.DmlOptions options = new Database.DmlOptions(); + options.optAllOrNone = true; + + try { + new DML().toInsert(accounts).options(options).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Required fields are missing: [Name]'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithPartialSuccess() { + // Setup + List accounts = new List{ + getAccount(1), + new Account() // Name is required + }; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + new DML().toInsert(accounts).allowPartialSuccess().commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only one account should be inserted, because one has missing required field.'); + } + + @IsTest + static void toUpdateWithPartialSuccess() { + // Setup + List accounts = new List{ getAccount(1), getAccount(2) }; + insert accounts; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + accounts[0].Name = null; + accounts[1].Name = 'Test Account 1 New Name'; + + new DML().toUpdate(accounts).allowPartialSuccess().commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, 'No DML statements should be made, because of the partial success.'); + Assert.areEqual( + 2, + [SELECT COUNT() FROM Account WHERE Name IN ('Test Account 1', 'Test Account 1 New Name')], + 'Accounts should be present with expected names after partial success update.' + ); + } + + @IsTest + static void toUpsertWithPartialSuccess() { + // Setup + List accounts = new List{ getAccount(1), getAccount(2) }; + insert accounts; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + + accounts[0].Name = null; // Name will not change, because it's set to null + accounts[1].Name = 'Test Account 1 New Name'; + + accounts.add(getAccount(3)); // New account + + new DML().toUpsert(accounts).allowPartialSuccess().commitWork(); + + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual( + 3, + [SELECT COUNT() FROM Account WHERE Name IN ('Test Account 1', 'Test Account 1 New Name', 'Test Account 3')], + 'Accounts should be upserted with expected names.' + ); + } + + @IsTest + static void toDeleteWithPartialSuccess() { + // Setup + List accounts = new List{ getAccount(1), getAccount(2) }; + insert accounts; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toDelete(accounts).allowPartialSuccess().commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be deleted.'); + } + + @IsTest + static void toHardDeleteWithPartialSuccess() { + // Setup + List accounts = new List{ getAccount(1), getAccount(2) }; + + insert accounts; + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toHardDelete(accounts).allowPartialSuccess().commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(2, dmlsAfter - dmlsBefore, '2 DML statements should be executed (1 delete + 1 hard delete).'); + Assert.areEqual(0, [SELECT COUNT() FROM Account], 'Accounts should be hard deleted.'); + // No assertion with ALL ROWS, because there is Salesforce error + } + + @IsTest + static void toInsertUserMode() { + // Setup + Case newCase = getCase(1); + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toInsert(newCase).commitWork(); // user mode by default + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithUserModeExplicitlySet() { + // Setup + Case newCase = getCase(1); + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toInsert(newCase).userMode().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toUpdateWithUserMode() { + // Setup + Case newCase = getCase(1); + insert newCase; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + newCase.Subject = 'Updated Test Case'; + new DML().toUpdate(newCase).commitWork(); // user mode by default + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toUpdateWithUserModeExplicitlySet() { + // Setup + Case newCase = getCase(1); + insert newCase; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + newCase.Subject = 'Updated Test Case'; + new DML().toUpdate(newCase).userMode().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toUpsertWithUserMode() { + // Setup + Case newCase1 = getCase(1); + Case newCase2 = getCase(2); + + insert newCase1; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + newCase1.Subject = 'Updated Test Case'; + + new DML().toUpsert(newCase1).toUpsert(newCase2).commitWork(); // user mode by default + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue( + expectedException.getMessage().contains('Operation failed due to fields being inaccessible on Sobject Case, check errors on Exception or Result'), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void toUpsertWithUserModeExplicitlySet() { + // Setup + Case newCase1 = getCase(1); + Case newCase2 = getCase(2); + + insert newCase1; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + newCase1.Subject = 'Updated Test Case'; + + new DML().toUpsert(newCase1).toUpsert(newCase2).userMode().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue( + expectedException.getMessage().contains('Operation failed due to fields being inaccessible on Sobject Case, check errors on Exception or Result'), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void toDeleteWithUserMode() { + // Setup + Case newCase = getCase(1); + insert newCase; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toDelete(newCase).commitWork(); // user mode by default + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toDeleteWithUserModeExplicitlySet() { + // Setup + Case newCase = getCase(1); + insert newCase; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toDelete(newCase).userMode().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toHardDeleteWithUserMode() { + // Setup + Case newCase = getCase(1); + insert newCase; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toHardDelete(newCase).commitWork(); // user mode by default + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Access to entity \'Case\' denied', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toHardDeleteWithSystemMode() { + // Setup + Case newCase = getCase(1); + insert newCase; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + new DML().toHardDelete(newCase).systemMode().withoutSharing().commitWork(); + } + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be hard deleted.'); + // No assertion with ALL ROWS, because there is Salesforce error + } + + @IsTest + static void toUndeleteWithUserMode() { + // Setup + Case newCase = getCase(1); + insert newCase; + + delete newCase; + + Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toUndelete(newCase).commitWork(); // user mode by default + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('invalid record id'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toUndeleteWithUserModeExplicitlySet() { + // Setup + Case newCase = getCase(1); + insert newCase; + + delete newCase; + + Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toUndelete(newCase).userMode().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('invalid record id'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toMergeWithUserMode() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + insert new List{ masterAccount, duplicateAccount }; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toMerge(masterAccount, duplicateAccount).commitWork(); // user mode by default + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + } + + @IsTest + static void toInsertSystemMode() { + // Setup + Case newCase = getCase(1); + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + new DML().toInsert(newCase).systemMode().commitWork(); + } + Test.stopTest(); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Case], 'Case should be inserted.'); + Assert.isNotNull(newCase.Id, 'Case should be inserted and have an Id.'); + } + + @IsTest + static void toUpdateWithSystemMode() { + // Setup + Case newCase = getCase(1); + insert newCase; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + newCase.Subject = 'Updated Test Case'; + + new DML().toUpdate(newCase).systemMode().withoutSharing().commitWork(); + } + Test.stopTest(); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Case], 'Case should be updated.'); + Assert.isNotNull(newCase.Id, 'Case should be updated and have an Id.'); + } + + @IsTest + static void toUpdateWithSystemModeAndWithSharing() { + // Setup + Contact newContact = getContact(1); + insert newContact; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + newContact.FirstName = 'Updated Test Contact'; + new DML().toUpdate(newContact).systemMode().withSharing().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('insufficient access rights on cross-reference id'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toUpsertWithSystemMode() { + // Setup + Case newCase1 = getCase(1); + Case newCase2 = getCase(2); + + insert newCase1; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + newCase1.Subject = 'Updated Test Case'; + + new DML().toUpsert(newCase1).toUpsert(newCase2).systemMode().withoutSharing().commitWork(); + } + Test.stopTest(); + + // Verify + Assert.areEqual(2, [SELECT COUNT() FROM Case], 'Cases should be upserted.'); + + Assert.isNotNull(newCase1.Id, 'Case 1 should be upserted and have an Id.'); + Assert.isNotNull(newCase2.Id, 'Case 2 should be upserted and have an Id.'); + } + + @IsTest + static void toUpsertWithSystemModeAndWithSharing() { + // Setup + Contact newContact1 = getContact(1); + Contact newContact2 = getContact(2); + + insert newContact1; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + newContact1.FirstName = 'Updated Test Contact'; + new DML().toUpsert(newContact1).toUpsert(newContact2).withSharing().systemMode().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('insufficient access rights on cross-reference id'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toDeleteWithSystemMode() { + // Setup + Case newCase = getCase(1); + insert newCase; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + new DML().toDelete(newCase).systemMode().withoutSharing().commitWork(); + } + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); + } + + @IsTest + static void toDeleteWithSystemModeAndWithSharing() { + // Setup + Case newCase = getCase(1); + insert newCase; + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toDelete(newCase).systemMode().withoutSharing().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Case should be deleted with system mode and without sharing.'); + Assert.isNull(expectedException, 'No exception should be thrown.'); + } + + @IsTest + static void toUndeleteWithSystemMode() { + // Setup + Case newCase = getCase(1); + insert newCase; + + delete newCase; + + Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + new DML().toUndelete(newCase).systemMode().withoutSharing().commitWork(); + } + Test.stopTest(); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Case], 'Cases should be undeleted.'); + } + + @IsTest + static void toUndeleteWithSystemModeAndWithSharing() { + // Setup + Case newCase = getCase(1); + insert newCase; + + delete newCase; + + Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Cases should be deleted.'); + + Exception expectedException = null; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + try { + new DML().toUndelete(newCase).systemMode().withSharing().commitWork(); + } catch (Exception e) { + expectedException = e; + } + } + Test.stopTest(); + + // Verify + Assert.areEqual(0, [SELECT COUNT() FROM Case], 'Case should not be undeleted, because user has no access with sharing mode.'); + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('insufficient access rights on object id'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toMergeWithSystemMode() { + // Setup + Account masterAccount = getAccount(1); + Account duplicateAccount = getAccount(2); + insert new List{ masterAccount, duplicateAccount }; + + // Test + Test.startTest(); + System.runAs(minimumAccessUser()) { + new DML().toMerge(masterAccount, duplicateAccount).systemMode().withoutSharing().commitWork(); + } + Test.stopTest(); + + // Verify + Assert.areEqual(1, [SELECT COUNT() FROM Account], 'Only master account should remain after merge.'); + } + + @IsTest + static void toInsertWithInvalidRelationshipSingleRecord() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.LastName, newAccount)).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Invalid argument: LastName. Field supplied is not a relationship field.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithInvalidTargetRelationshipFieldSingleRecord() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.LastName, Account.Name, 'Test')).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Invalid argument: relationshipField. Field supplied is not a relationship field.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void recordWithEmptyId() { + // Setup + Account account = getAccount(1); + + // Test + Test.startTest(); + Exception expectedException = null; + try { + new DML().toInsert(DML.Record(account.Id)).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.isTrue(expectedException.getMessage().contains('Invalid argument: recordId. Record ID cannot be null.'), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithInvalidExternalRelationshipSingleRecord() { + // Setup + Account newAccount = getAccount(1); + Contact newContact = getContact(1); + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Record(newContact).withRelationship(Contact.AccountId, Account.Name, 'Test')).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void toInsertWithExistingIds() { + // Setup + Account account = getAccount(1); + insert account; + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toInsert(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only new records can be registered as new.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithInvalidTargetRelationshipFieldMultipleRecords() { + // Setup + Account newAccount = getAccount(1); + Contact newContact1 = getContact(1); + Contact newContact2 = getContact(2); + + List contacts = new List{ newContact1, newContact2 }; + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.LastName, newAccount)).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Invalid argument: LastName. Field supplied is not a relationship field.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toInsertWithInvalidExternalRelationshipMultipleRecords() { + // Setup + Account newAccount = getAccount(1); + Contact newContact1 = getContact(1); + Contact newContact2 = getContact(2); + + List contacts = new List{ newContact1, newContact2 }; + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.LastName, Account.Name, 'Test')).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Invalid argument: relationshipField. Field supplied is not a relationship field.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void toInsertWithInvalidExternalIdFieldMultipleRecords() { + // Setup + Account newAccount = getAccount(1); + Contact newContact1 = getContact(1); + Contact newContact2 = getContact(2); + + List contacts = new List{ newContact1, newContact2 }; + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + new DML().toInsert(newAccount).toInsert(DML.Records(contacts).withRelationship(Contact.AccountId, Account.Name, 'Test')).commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Invalid argument: externalIdField. Field supplied is not marked as an External Identifier.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void toUpdateSingleRecordTwice() { + // Setup + Account account1 = getAccount(1); + insert account1; + + // Test + Test.startTest(); + Exception expectedException = null; + + try { + account1.Name = 'Updated Test Account'; + + DML db = new DML(); + + db.toUpdate(account1); + + Account duplicatedAccount = new Account(Id = account1.Id, Name = 'Updated Test Account 2'); + + db.toUpdate(duplicatedAccount); + + db.commitWork(); + } catch (Exception e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual( + 'Duplicate records found during registration. Fix the code or use the combineOnDuplicate() method.', + expectedException.getMessage(), + 'Expected exception message should be thrown.' + ); + } + + @IsTest + static void toUpdateWithoutExistingIds() { + // Setup + Account account = getAccount(1); + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUpdate(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only existing records can be updated.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void toUndeleteWithoutExistingIds() { + // Setup + Account account = getAccount(1); + + DmlException expectedException = null; + + // Test + Test.startTest(); + try { + new DML().toUndelete(account).commitWork(); + } catch (DmlException e) { + expectedException = e; + } + Test.stopTest(); + + // Verify + Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); + Assert.areEqual('Only deleted records can be undeleted.', expectedException.getMessage(), 'Expected exception message should be thrown.'); + } + + @IsTest + static void commitHook() { + // Setup + Account account = getAccount(1); + + MyHook hook = new MyHook(); + + // Test + Test.startTest(); + Integer dmlsBefore = Limits.getDMLStatements(); + new DML().toInsert(account).commitHook(hook).commitWork(); + Integer dmlsAfter = Limits.getDMLStatements(); + Test.stopTest(); + + // Verify + Assert.areEqual(1, dmlsAfter - dmlsBefore, '1 DML statement should be executed.'); + Assert.isTrue(hook.beforeCalled, 'Before hook should be called.'); + Assert.isTrue(hook.afterCalled, 'After hook should be called.'); + } + + public class MyHook implements DML.Hook { + private Boolean beforeCalled = false; + private Boolean afterCalled = false; + + public void before() { + beforeCalled = true; + } + + public void after(DML.Result result) { + afterCalled = true; + + Assert.isNotNull(result, 'Result should not be null.'); + } + } + + // HELPERS + + static Account getAccount(Integer index) { + return new Account(Name = 'Test Account ' + index); + } + + static Contact getContact(Integer index) { + return new Contact(FirstName = 'Test ' + index, LastName = 'Contact ' + index); + } + + static Opportunity getOpportunity(Integer index) { + return new Opportunity(Name = 'Test Opportunity ' + index, CloseDate = Date.today(), StageName = 'Prospecting'); + } + + static Lead getLead(Integer index) { + return new Lead(FirstName = 'Test ' + index, LastName = 'Lead ' + index, Company = 'Test Company ' + index); + } + + static Case getCase(Integer index) { + return new Case(Status = 'New', Origin = 'Web', Subject = 'Test ' + index); + } + + static Account insertAccount() { + Account account = new Account(Name = 'Test Account'); + insert account; + + return account; + } + + @SuppressWarnings('PMD.AvoidNonRestrictiveQueries') + static List getAccounts() { + return [SELECT Id, Name FROM Account]; + } + + static List insertAccounts() { + List accounts = new List{ new Account(Name = 'Test Account 1'), new Account(Name = 'Test Account 2'), new Account(Name = 'Test Account 3') }; + insert accounts; + + return accounts; + } + + static User minimumAccessUser() { + return new User( + Alias = 'newUser', + Email = 'newuser@testorg.com', + EmailEncodingKey = 'UTF-8', + LastName = 'Testing', + LanguageLocaleKey = 'en_US', + LocaleSidKey = 'en_US', + Profile = new Profile(Name = 'Minimum Access - Salesforce'), + TimeZoneSidKey = 'America/Los_Angeles', + UserName = 'btcdmllibuser@testorg.com' + ); + } +} diff --git a/package/main/default/classes/DML_Full_Test.cls-meta.xml b/package/main/default/classes/DML_Full_Test.cls-meta.xml new file mode 100644 index 0000000..82775b9 --- /dev/null +++ b/package/main/default/classes/DML_Full_Test.cls-meta.xml @@ -0,0 +1,5 @@ + + + 65.0 + Active + diff --git a/sfdx-project.json b/sfdx-project.json index 983ed93..cbf66ef 100644 --- a/sfdx-project.json +++ b/sfdx-project.json @@ -3,14 +3,14 @@ { "versionName": "ver 3.0.1", "versionNumber": "3.0.1.NEXT", - "path": "force-app", - "default": true, + "path": "package", + "default": false, "package": "DML Lib", "versionDescription": "" }, { - "path": "internal", - "default": false + "path": "force-app", + "default": true } ], "name": "dml-lib", From db18015e21dca613485024318faf146d6b534c40 Mon Sep 17 00:00:00 2001 From: Piotr PG Gajek Date: Tue, 12 May 2026 21:49:08 +0200 Subject: [PATCH 3/4] Refactoring --- force-app/main/default/classes/DML.cls | 2 +- force-app/main/default/classes/DML_Test.cls | 38 ++++++++++--------- package/main/default/classes/DML.cls | 2 +- .../main/default/classes/DML_Full_Test.cls | 38 ++++++++++--------- 4 files changed, 42 insertions(+), 38 deletions(-) diff --git a/force-app/main/default/classes/DML.cls b/force-app/main/default/classes/DML.cls index 8e3c2fe..3a5b509 100644 --- a/force-app/main/default/classes/DML.cls +++ b/force-app/main/default/classes/DML.cls @@ -221,7 +221,7 @@ public inherited sharing class DML implements Commitable { public static DML.Result retrieveResultFor(String dmlIdentifier) { if (!dmlIdentifierToResult.containsKey(dmlIdentifier)) { - throw new DmlException('No result found for dml identifier: ' + dmlIdentifier); + return new DmlResult(); } return dmlIdentifierToResult.get(dmlIdentifier); } diff --git a/force-app/main/default/classes/DML_Test.cls b/force-app/main/default/classes/DML_Test.cls index 0b0212d..52c8e87 100644 --- a/force-app/main/default/classes/DML_Test.cls +++ b/force-app/main/default/classes/DML_Test.cls @@ -377,21 +377,21 @@ private class DML_Test { @IsTest static void retrieveResultForWhenIncorrectIdentifierIsUsed() { - // Setup - Exception expectedException = null; - // Test Test.startTest(); - try { - DML.retrieveResultFor('dmlMockId'); - } catch (Exception e) { - expectedException = e; - } + DML.Result result = DML.retrieveResultFor('dmlMockId'); Test.stopTest(); // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('No result found for dml identifier: dmlMockId', expectedException.getMessage(), 'Expected exception message should be thrown.'); + Assert.isNotNull(result, 'Empty result should be returned for an unknown identifier.'); + Assert.areEqual(0, result.all().size(), 'Result should contain 0 operation results.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); } @IsTest @@ -3390,17 +3390,19 @@ private class DML_Test { // Test Test.startTest(); - Exception expectedException = null; - try { - DML.retrieveResultFor('dmlMockId'); - } catch (Exception e) { - expectedException = e; - } + DML.Result result = DML.retrieveResultFor('dmlMockId'); Test.stopTest(); // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('No result found for dml identifier: dmlMockId'), 'Expected exception message should be thrown.'); + Assert.isNotNull(result, 'Empty result should be returned when no commitWork has run for the identifier.'); + Assert.areEqual(0, result.all().size(), 'Result should contain 0 operation results.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); } diff --git a/package/main/default/classes/DML.cls b/package/main/default/classes/DML.cls index 34abbdd..12acc6b 100644 --- a/package/main/default/classes/DML.cls +++ b/package/main/default/classes/DML.cls @@ -221,7 +221,7 @@ global inherited sharing class DML implements Commitable { global static DML.Result retrieveResultFor(String dmlIdentifier) { if (!dmlIdentifierToResult.containsKey(dmlIdentifier)) { - throw new DmlException('No result found for dml identifier: ' + dmlIdentifier); + return new DmlResult(); } return dmlIdentifierToResult.get(dmlIdentifier); } diff --git a/package/main/default/classes/DML_Full_Test.cls b/package/main/default/classes/DML_Full_Test.cls index 5df54bb..e2836a0 100644 --- a/package/main/default/classes/DML_Full_Test.cls +++ b/package/main/default/classes/DML_Full_Test.cls @@ -2677,21 +2677,21 @@ private class DML_Full_Test { @IsTest static void retrieveResultForWhenIncorrectIdentifierIsUsed() { - // Setup - Exception expectedException = null; - // Test Test.startTest(); - try { - DML.retrieveResultFor('dmlMockId'); - } catch (Exception e) { - expectedException = e; - } + DML.Result result = DML.retrieveResultFor('dmlMockId'); Test.stopTest(); // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.areEqual('No result found for dml identifier: dmlMockId', expectedException.getMessage(), 'Expected exception message should be thrown.'); + Assert.isNotNull(result, 'Empty result should be returned for an unknown identifier.'); + Assert.areEqual(0, result.all().size(), 'Result should contain 0 operation results.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); } @IsTest @@ -6488,17 +6488,19 @@ private class DML_Full_Test { // Test Test.startTest(); - Exception expectedException = null; - try { - DML.retrieveResultFor('dmlMockId'); - } catch (Exception e) { - expectedException = e; - } + DML.Result result = DML.retrieveResultFor('dmlMockId'); Test.stopTest(); // Verify - Assert.isNotNull(expectedException, 'Expected exception to be thrown.'); - Assert.isTrue(expectedException.getMessage().contains('No result found for dml identifier: dmlMockId'), 'Expected exception message should be thrown.'); + Assert.isNotNull(result, 'Empty result should be returned when no commitWork has run for the identifier.'); + Assert.areEqual(0, result.all().size(), 'Result should contain 0 operation results.'); + Assert.areEqual(0, result.inserts().size(), 'Inserted operation result should contain 0 results.'); + Assert.areEqual(0, result.upserts().size(), 'Upserted operation result should contain 0 results.'); + Assert.areEqual(0, result.updates().size(), 'Updated operation result should contain 0 results.'); + Assert.areEqual(0, result.deletes().size(), 'Deleted operation result should contain 0 results.'); + Assert.areEqual(0, result.undeletes().size(), 'Undeleted operation result should contain 0 results.'); + Assert.areEqual(0, result.merges().size(), 'Merged operation result should contain 0 results.'); + Assert.areEqual(0, result.events().size(), 'Published operation result should contain 0 results.'); } @IsTest From 5f4f774341aae5d0354ef17561c3c0bd8b4be60c Mon Sep 17 00:00:00 2001 From: Piotr PG Gajek Date: Tue, 12 May 2026 21:54:27 +0200 Subject: [PATCH 4/4] 2026 --- LICENSE | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 16bc698..a63318b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev) +Copyright (c) 2026 Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 86787b5..bb70507 100644 --- a/README.md +++ b/README.md @@ -63,4 +63,4 @@ Visit the [documentation](https://dml.beyondthecloud.dev) to view the full docum ## License notes: - For proper license management each repository should contain LICENSE file similar to this one. -- each original class should contain copyright mark: © Copyright 2025, Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev) +- each original class should contain copyright mark: © Copyright 2026, Beyond The Cloud Sp. z o.o. (BeyondTheCloud.Dev)