Skip to content

Commit

Permalink
Merge pull request apex-enterprise-patterns#3 from OrtooApps/feature/…
Browse files Browse the repository at this point in the history
…adding-crud-and-dml-fls

Feature/adding crud and dml fls
  • Loading branch information
rob-baillie-ortoo authored Dec 13, 2021
2 parents 0797902 + d0c5af3 commit 144077b
Show file tree
Hide file tree
Showing 27 changed files with 2,861 additions and 70 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/create-org-and-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ jobs:
# Run All Unit Tests

- name: Run All Unit Tests
run: sfdx force:apex:test:run -r human -u "${{env.ORG_ALIAS_PREFIX}}${{github.run_number}}" --wait 20 | grep -v ' Pass '; test ${PIPESTATUS[0]} -eq 0
run: sfdx force:apex:test:run -r human -u "${{env.ORG_ALIAS_PREFIX}}${{github.run_number}}" --codecoverage --wait 20 | grep -v ' Pass '; test ${PIPESTATUS[0]} -eq 0

# Delete Scratch Org

Expand Down
61 changes: 22 additions & 39 deletions TODO.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ Licenses that are needed with the source code and binary:

Look at the use of 'MockDatabase' in fflib

* To finalise the core architecture:
* Decide on FLS standards
* Do we need to have a non all-or-nothing version of commitWork?
Test:
Unit of Work: elevatedContext

Add reference to disabling individual trigger events in tests:
https://andyinthecloud.com/2016/04/13/disabling-trigger-events-in-apex-enterprise-patterns/
Look at:
SobjectUtils.getSobjectName

Add to Framework plan
* Ability to configure different factory mappings per user / permission set / custom permission / whatever
* To finalise the core architecture:
* Do we need to have a non all-or-nothing version of commitWork?

Add to documentation
* Wrapping exceptions on the way out of services
Expand All @@ -28,24 +27,23 @@ Add to documentation
* Do not do domain logic in them

* Using the Mock Registarar
* Describe the Application Factories

From Utilities, things that may be useful:
* getReferenceObjectAPIName
* getObjName - get the object name from an Id
* getLabel / getObjectLabel - get the label for an sobject
* getFieldLabel
* delimitedStringToSet and reverse
* escaping single quotes - in both directions?
* unitsBetweenDateTime
* emailAddressIsValid / emailAddressListIsValid
* sObjectIsCustom / sObjectIsCustomfromAPIName
* IsfieldFilterable
* isFieldCustom
* idIsValid
* getCrossObjectAPIName
* objectFieldExist
* sortSelectOptions - complete re-write
* getReferenceObjectAPIName
* getObjName - get the object name from an Id
* getLabel / getObjectLabel - get the label for an sobject
* getFieldLabel
* delimitedStringToSet and reverse
* escaping single quotes - in both directions?
* unitsBetweenDateTime
* emailAddressIsValid / emailAddressListIsValid
* sObjectIsCustom / sObjectIsCustomfromAPIName
* IsfieldFilterable
* isFieldCustom
* idIsValid
* getCrossObjectAPIName
* objectFieldExist
* sortSelectOptions - complete re-write

Write tests for the SOQL generation in the criteria library

Expand All @@ -72,10 +70,6 @@ Bad Smells - strung out calls to describe methods - put them into SobjectUtils

* Question: How do we handle Constants - where are they defined?
* Question: How do we handle Exceptions:
* Question: How do we handle FLS - what are the rules?

* Where are they defined?
* Do we want to pass a single type of exception (e.g. ServiceException back to the client)?
* Do we handle individual types of exception in the Service?
* Do we pass any of the domain exceptions back

Expand All @@ -86,22 +80,11 @@ Bad Smells - strung out calls to describe methods - put them into SobjectUtils

* Question: Do you need Heap size management rules -


* Produce test-case for lack of clickthrough and raise bug on VSCode

* How do we handle constants?

* Dynamic building of the Unit Of Work for processing data

Standards?
* Selector name - singular or plural?
* When you have a class that wraps Sobject, what are the naming convensions - which one is called 'xxxObject'?
* Creating a service that performs DML - always provide an unwrapped version that takes a UOW
* Interfaces start with an I

Notes:
* Services should always be designed by seniors - at least which services exist

What's missing:
* Custom metadata driven execution of trigger code (register multiple trigger handlers)
* How does QueryHandler drop into this? - Maintenance of change of state
* Services should always be designed by seniors - at least which services exist
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public abstract with sharing class fflib_SObjectSelector
/**
* Enforce FLS Security
**/
private Boolean m_enforceFLS = false;
protected Boolean m_enforceFLS = false;

/**
* Enforce CRUD Security
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,10 @@ public inherited sharing class FrameworkErrorCodes {
public final static String CONFIGURATION_WITH_INVALID_TYPE = 'APP-00001';
public final static String CONFIGURATION_WITH_INVALID_CLASS = 'APP-00002';
public final static String CONFIGURATION_WITH_INVALID_SOBJECT_TYPE = 'APP-00003';

public final static String DML_ON_INACCESSIBLE_FIELDS = '00000';
public final static String DML_INSERT_NOT_ALLOWED = '00001';
public final static String DML_UPDATE_NOT_ALLOWED = '00002';
public final static String DML_DELETE_NOT_ALLOWED = '00003';
public final static String DML_PUBLISH_NOT_ALLOWED = '00004';
}
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,27 @@ public virtual class ortoo_Exception extends Exception implements IRenderableMes
return returnList;
}

/**
* Will regenerate the stack trace string that is used for generating the StackTrace object.
*
* Is useful when you want to raise an exception in one method and have it report as being thrown by
* a parent method - for example in a handler that has been delegated to throw exceptions.
* See SecureDml.ErrorOnFlsViolationHandler.handleInaccessibleFields as an example.
*
* @param Integer The number of levels upwards in the stack trace to skip.
* @return ortoo_Exception Itself, allowing for a fluent interface
*/
public ortoo_Exception regenerateStackTraceString( Integer levelsToSkip )
{
stackTraceString = createStackTraceString( levelsToSkip + 1 );
return this;
}

private String createStackTraceString( Integer levelsToSkip )
{
return new StackTrace( levelsToSkip+1 ).getFullStackTraceString(); // Since custom exceptions have some problems getting their stack trace strings set,
// we need to get the Stack Trace string from the generic utility, stating that the top
// level method (this one) should be ignored.
return new StackTrace( levelsToSkip + 1 ).getFullStackTraceString(); // Since custom exceptions have some problems getting their stack trace strings set,
// we need to get the Stack Trace string from the generic utility, stating that the top
// level method (this one) should be ignored.
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,50 @@ public with sharing class ortoo_ExceptionTest {
Amoss_Asserts.assertContains( 'ortoo_ExceptionTest.getStackTraceString_whenSubclassedAndMultipleMethods_willReturnTheStackTrace', stackTrace, 'getStackTraceString, when the exception is subclassed and there are multiple layers of methods, will return the Stack Trace based on where the exception was raised - with each level (method 3)' );
}

@isTest
private static void regenerateStackTraceString_willCreateANewStackTraceFromTheGivenPoint()
{
String stackTrace;
Test.startTest();
try
{
throwASubclassedException();
}
catch ( SubclassedException e )
{
e.regenerateStackTraceString( 0 );
stackTrace = e.getStackTraceString();
}
Test.stopTest();

Amoss_Asserts.assertDoesNotContain( '<init>', stackTrace, 'regenerateStackTraceString, when called, will create a new stack trace from the given point' );
Amoss_Asserts.assertDoesNotContain( 'ortoo_ExceptionTest.throwASubclassedExceptionInnerMethodCall', stackTrace, 'regenerateStackTraceString, when called, will create a new stack trace from the given point (method 1)' );
Amoss_Asserts.assertDoesNotContain( 'ortoo_ExceptionTest.throwASubclassedException', stackTrace, 'regenerateStackTraceString, when called, will create a new stack trace from the given point (method 2)' );
Amoss_Asserts.assertContains( 'ortoo_ExceptionTest.regenerateStackTraceString_willCreateANewStackTraceFromTheGivenPoint', stackTrace, 'regenerateStackTraceString, when called, will create a new stack trace from the given point (actual spot called)' );
}

@isTest
private static void regenerateStackTraceString_whenPassedANumber_willCreateANewStackTraceWithLevelsSkipped()
{
Integer levelsToSkip = 1;

String stackTrace;
Test.startTest();
try
{
throwASubclassedException( levelsToSkip );
}
catch ( SubclassedException e )
{
stackTrace = e.getStackTraceString();
}
Test.stopTest();

Amoss_Asserts.assertDoesNotContain( 'ortoo_ExceptionTest.throwASubclassedExceptionInnerMethodCall', stackTrace, 'regenerateStackTraceString, when called with a number of levels to skipp, will create a new stack trace from the given point, skipping the specified number of levels (1st level is skipped)' );
Amoss_Asserts.assertContains( 'ortoo_ExceptionTest.throwASubclassedException', stackTrace, 'regenerateStackTraceString, when called with a number of levels to skipp, will create a new stack trace from the given point, skipping the specified number of levels (2nd level is not skipped)' );
Amoss_Asserts.assertContains( 'ortoo_ExceptionTest.regenerateStackTraceString_whenPassedANumber_willCreateANewStackTraceWithLevelsSkipped', stackTrace, 'regenerateStackTraceString, when called with a number of levels to skipp, will create a new stack trace from the given point, skipping the specified number of levels (3rd level is not skipped)' );
}

@isTest
private static void addContext_whenCalled_willAddItToTheExceptionWithStackInfo() // NOPMD: Test method name format
{
Expand Down Expand Up @@ -261,6 +305,48 @@ public with sharing class ortoo_ExceptionTest {
System.assertEquals( true, exceptionCaught, 'next, when there are no entries in the list, will throw a NoSuchElementException exception' );
}

@isTest
private static void getMessageDetails_whenMessageDetailsAdded_willReturnThoseDetails() // NOPMD: Test method name format
{
ortoo_Exception exceptionUnderTest = new ortoo_Exception( 'message' );

List<Sobject> objectContexts = new List<Sobject>
{
new Contact( Id = TestIdUtils.generateId( Contact.sobjectType ) ),
new Contact( Id = TestIdUtils.generateId( Contact.sobjectType ) ),
new Contact( Id = TestIdUtils.generateId( Contact.sobjectType ) )
};

List<MessageDetail> messageDetails = new List<MessageDetail>
{
new MessageDetail( objectContexts[0], 'message1' ),
new MessageDetail( objectContexts[1], 'message2' ),
new MessageDetail( objectContexts[1], 'message3' ),
new MessageDetail( objectContexts[0], 'message4' )
};

Test.startTest();
exceptionUnderTest.setMessageDetails( messageDetails );
List<MessageDetail> returnedMessageDetails = exceptionUnderTest.getMessageDetails();


Test.stopTest();

System.assertEquals( messageDetails, returnedMessageDetails, 'getMessageDetails, when some message details have been added, will return those details' );
}

@isTest
private static void getMessageDetails_whenNoMessageDetailsAdded_willReturnAnEmptyList() // NOPMD: Test method name format
{
ortoo_Exception exceptionUnderTest = new ortoo_Exception( 'message' );

Test.startTest();
List<MessageDetail> returnedMessageDetails = exceptionUnderTest.getMessageDetails();
Test.stopTest();

System.assertEquals( 0, returnedMessageDetails.size(), 'getMessageDetails, when no message details have been added, will return an empty list' );
}

@isTest
private static void getObjectContext_whenMessageDetailsAdded_willReturnTheSobjects() // NOPMD: Test method name format
{
Expand Down Expand Up @@ -364,11 +450,21 @@ public with sharing class ortoo_ExceptionTest {
throw new SubclassedException();
}

private static void throwASubclassedExceptionInnerMethodCall( Integer levelsToSkip )
{
throw new SubclassedException().regenerateStackTraceString( levelsToSkip );
}

private static void throwASubclassedException()
{
throwASubclassedExceptionInnerMethodCall();
}

private static void throwASubclassedException( Integer levelsToSkip )
{
throwASubclassedExceptionInnerMethodCall( levelsToSkip);
}

private static String getClassName()
{
return String.valueOf( ortoo_ExceptionTest.class );
Expand Down
Loading

0 comments on commit 144077b

Please sign in to comment.