Skip to content

Commit 4b2968e

Browse files
Started to test SecureDml
Fix issue where CUD would be checked by stripInaccessible even when we said to not check CUD
1 parent 98d3472 commit 4b2968e

File tree

3 files changed

+253
-6
lines changed

3 files changed

+253
-6
lines changed

TODO.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Test:
1313

1414
Look at:
1515
SobjectUtils.getSobjectName
16+
Do we need to add 'emptyRecycleBin' too?
1617

1718
Test Manually:
1819
Implementation os SecureDml in general - does it actually work

framework/default/ortoo-core/default/classes/fflib-extension/SecureDml.cls

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// TODO: test
21
/**
32
* Is an implementation of the IDml interface used to manage the DML operations in an SObject Unit of Work.
43
*
@@ -268,7 +267,7 @@ public inherited sharing virtual class SecureDml extends fflib_SobjectUnitOfWork
268267
private void checkFls( List<Sobject> objList, AccessType mode )
269268
{
270269
String sobjectTypeName = SobjectUtils.getSobjectName( objList[0] );
271-
SObjectAccessDecision securityDecision = Security.stripInaccessible( mode, objList );
270+
SObjectAccessDecision securityDecision = Security.stripInaccessible( mode, objList, false );
272271
Set<String> removedFields = securityDecision.getRemovedFields().get( sobjectTypeName );
273272

274273
if ( removedFields != null && !removedFields.isEmpty() )
@@ -420,17 +419,20 @@ public inherited sharing virtual class SecureDml extends fflib_SobjectUnitOfWork
420419
}
421420
}
422421

423-
private virtual Boolean userCanCreate( Sobject record )
422+
// This method cannot be reliably unit tested in a framework that does not inculde profiles and suchlike
423+
protected virtual Boolean userCanCreate( Sobject record )
424424
{
425425
return SobjectUtils.isCreateable( record );
426426
}
427427

428-
private virtual Boolean userCanUpdate( Sobject record )
428+
// This method cannot be reliably unit tested in a framework that does not inculde profiles and suchlike
429+
protected virtual Boolean userCanUpdate( Sobject record )
429430
{
430431
return SobjectUtils.isUpdateable( record );
431432
}
432433

433-
private virtual Boolean userCanDelete( Sobject record )
434+
// This method cannot be reliably unit tested in a framework that does not inculde profiles and suchlike
435+
protected virtual Boolean userCanDelete( Sobject record )
434436
{
435437
return SobjectUtils.isUpdateable( record );
436438
}
Lines changed: 245 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,249 @@
1-
1+
// If there is any risk that this test would stop working in a given App implementation, then it should be rewritten or removed.
2+
// It is written in a way that assumes that Accounts and Contacts are configured to allow for the most trivial records to be created
23
@isTest
34
private without sharing class SecureDmlTest
45
{
6+
@isTest
7+
private static void dmlInsert_whenTheUserCanCreateTheRecords_willInsertTheRecords() // NOPMD: Test method name format
8+
{
9+
List<Account> accounts = new List<Account>
10+
{
11+
(Account)new FAccount().name( 'Account1' ).toPersistableSobject(),
12+
(Account)new FAccount().name( 'Account2' ).toPersistableSobject()
13+
};
14+
15+
Test.startTest();
16+
17+
SecureDml dml = new TestableSecureDml();
18+
dml.dmlInsert( accounts );
19+
20+
Test.stopTest();
21+
22+
System.assertNotEquals( null, accounts[0].Id, 'dmlInsert, when the user can create the records, will insert the records, setting the Id on them (0)' );
23+
System.assertNotEquals( null, accounts[1].Id, 'dmlInsert, when the user can create the records, will insert the records, setting the Id on them (1)' );
24+
25+
List<Account> createdAccounts = getAccounts();
26+
27+
System.assertEquals( 'Account1', accounts[0].Name, 'dmlInsert, when the user can create the records, will insert the records, setting the fields on the records that are created (0)' );
28+
System.assertEquals( 'Account2', accounts[1].Name, 'dmlInsert, when the user can create the records, will insert the records, setting the fields on the records that are created (1)' );
29+
}
30+
31+
@isTest
32+
private static void dmlInsert_whenTheUserCannotCreateRecords_willThrowAnException() // NOPMD: Test method name format
33+
{
34+
List<Account> accounts = new List<Account>
35+
{
36+
(Account)new FAccount().name( 'Account1' ).toPersistableSobject(),
37+
(Account)new FAccount().name( 'Account2' ).toPersistableSobject()
38+
};
39+
40+
Test.startTest();
41+
ortoo_Exception thrownException;
42+
try
43+
{
44+
SecureDml dml = new TestableSecureDml();
45+
((TestableSecureDml)dml).canCreate = false;
46+
47+
dml.dmlInsert( accounts );
48+
}
49+
catch ( SecureDml.SecureDmlException e )
50+
{
51+
thrownException = e;
52+
}
53+
Test.stopTest();
54+
55+
System.assertNotEquals( null, thrownException, 'dmlInsert, when the user cannot create records, will throw an exception' );
56+
57+
ortoo_Exception.Contexts contexts = thrownException.getContexts();
58+
ortoo_Exception.Context context;
59+
60+
context = contexts.next();
61+
System.assertEquals( 'sobjectTypeName', context.getName(), 'dmlInsert, when the user cannot create records, will throw an exception with a context named sobjectTypeName' );
62+
System.assertEquals( Account.getSObjectType().getDescribe().getName(), context.getValue(), 'dmlInsert, when the user cannot create records, will throw an exception with a context named sobjectTypeName set to the name of the SObject' );
63+
64+
context = contexts.next();
65+
System.assertEquals( 'records', context.getName(), 'dmlInsert, when the user cannot create records, will throw an exception with a context named records' );
66+
System.assertEquals( accounts, context.getValue(), 'dmlInsert, when the user cannot create records, will throw an exception with a context named records set to the records that where sent' );
67+
68+
System.assertEquals( 'dmlInsert', thrownException.getStackTrace().getInnermostMethodName(), 'dmlInsert, when the user cannot create records, will throw an exception with the stack trace pointing to the insert method' );
69+
}
70+
71+
@isTest
72+
private static void dmlInsert_whenTheUserCanNotCreateButCudOff_willInsertTheRecords() // NOPMD: Test method name format
73+
{
74+
List<Account> accounts = new List<Account>
75+
{
76+
(Account)new FAccount().name( 'Account1' ).toPersistableSobject(),
77+
(Account)new FAccount().name( 'Account2' ).toPersistableSobject()
78+
};
79+
80+
Test.startTest();
81+
82+
SecureDml dml = new TestableSecureDml();
83+
84+
((TestableSecureDml)dml).canCreate = false; // mimics not having write access
85+
86+
dml.ignoreCudSettings();
87+
dml.dmlInsert( accounts );
88+
89+
Test.stopTest();
90+
91+
System.assertNotEquals( null, accounts[0].Id, 'dmlInsert, when the user cannot create the records but cud switched off, will insert the records, setting the Id on them (0)' );
92+
System.assertNotEquals( null, accounts[1].Id, 'dmlInsert, when the user cannot create the records but cud switched off, will insert the records, setting the Id on them (1)' );
93+
}
94+
95+
@isTest
96+
private static void dmlInsert_whenTheUserCanNotCreateButCudOffForThatObject_willInsertTheRecords() // NOPMD: Test method name format
97+
{
98+
List<Account> accounts = new List<Account>
99+
{
100+
(Account)new FAccount().name( 'Account1' ).toPersistableSobject(),
101+
(Account)new FAccount().name( 'Account2' ).toPersistableSobject()
102+
};
103+
104+
Test.startTest();
105+
106+
SecureDml dml = new TestableSecureDml()
107+
.ignoreCudSettingsFor( Account.SobjectType );
108+
109+
((TestableSecureDml)dml).canCreate = false; // mimics not having write access
110+
111+
dml.dmlInsert( accounts );
112+
113+
Test.stopTest();
114+
115+
System.assertNotEquals( null, accounts[0].Id, 'dmlInsert, when the user cannot create the records but cud switched off for that sobject type, will insert the records, setting the Id on them (0)' );
116+
System.assertNotEquals( null, accounts[1].Id, 'dmlInsert, when the user cannot create the records but cud switched off for that sobject type, will insert the records, setting the Id on them (1)' );
117+
}
118+
119+
@isTest
120+
private static void dmlInsert_whenTheUserCanNotCreateButCudOffForMultipleObjects_willInsertTheRecords() // NOPMD: Test method name format
121+
{
122+
List<Account> accounts = new List<Account>
123+
{
124+
(Account)new FAccount().name( 'Account1' ).toPersistableSobject(),
125+
(Account)new FAccount().name( 'Account2' ).toPersistableSobject()
126+
};
127+
128+
Test.startTest();
129+
130+
SecureDml dml = new TestableSecureDml()
131+
.ignoreCudSettingsFor( Account.SobjectType )
132+
.ignoreCudSettingsFor( Contact.SobjectType );
133+
134+
((TestableSecureDml)dml).canCreate = false; // mimics not having write access
135+
136+
dml.dmlInsert( accounts );
137+
138+
Test.stopTest();
139+
140+
System.assertNotEquals( null, accounts[0].Id, 'dmlInsert, when the user cannot create the records but cud switched off for multiple sobject types, including that one, will insert the records, setting the Id on them (0)' );
141+
System.assertNotEquals( null, accounts[1].Id, 'dmlInsert, when the user cannot create the records but cud switched off for multiple sobject types, including that one, will insert the records, setting the Id on them (1)' );
142+
}
143+
144+
@isTest
145+
private static void dmlInsert_whenTheUserCannotCreateRecordsAndCudOfForOtherObjects_willThrowAnException() // NOPMD: Test method name format
146+
{
147+
List<Account> accounts = new List<Account>
148+
{
149+
(Account)new FAccount().name( 'Account1' ).toPersistableSobject(),
150+
(Account)new FAccount().name( 'Account2' ).toPersistableSobject()
151+
};
152+
153+
Test.startTest();
154+
ortoo_Exception thrownException;
155+
try
156+
{
157+
SecureDml dml = new TestableSecureDml()
158+
.ignoreCudSettingsFor( Contact.sobjectType );
159+
160+
((TestableSecureDml)dml).canCreate = false;
161+
162+
dml.dmlInsert( accounts );
163+
}
164+
catch ( SecureDml.SecureDmlException e )
165+
{
166+
thrownException = e;
167+
}
168+
Test.stopTest();
169+
170+
System.assertNotEquals( null, thrownException, 'dmlInsert, when the user cannot create records and cud is off for other objects, will still throw an exception' );
171+
}
172+
173+
@isTest
174+
private static void dmlInsert_whenGivenAnEmptyList_willDoNothing() // NOPMD: Test method name format
175+
{
176+
List<Account> emptyList = new List<Account>();
177+
178+
Test.startTest();
179+
180+
SecureDml dml = new TestableSecureDml();
181+
dml.dmlInsert( emptyList );
182+
183+
Test.stopTest();
184+
185+
System.assertEquals( 0, Limits.getDmlStatements(), 'dmlInsert, when given an empty list, will not issue any DML' );
186+
}
187+
188+
@isTest
189+
private static void dmlInsert_whenGivenAnEmptyListAndCreateIsNotAllowed_willDoNothing() // NOPMD: Test method name format
190+
{
191+
List<Account> emptyList = new List<Account>();
192+
193+
Test.startTest();
194+
195+
SecureDml dml = new TestableSecureDml();
196+
197+
((TestableSecureDml)dml).canCreate = false;
198+
199+
dml.dmlInsert( emptyList );
200+
201+
Test.stopTest();
202+
203+
System.assertEquals( 0, Limits.getDmlStatements(), 'dmlInsert, when given an empty list of objects the user cannot create, will not issue any DML or throw an exception' );
204+
}
205+
206+
private static List<Account> getAccounts()
207+
{
208+
return [SELECT Name FROM Account ORDER BY Name];
209+
}
210+
211+
// version of SecureDml that allows us to override the checks on whether
212+
// the current user can create / update / delete
213+
private inherited sharing class TestableSecureDml extends SecureDml
214+
{
215+
public Boolean canCreate = true;
216+
public Boolean canUpdate = true;
217+
public Boolean canDelete = true;
218+
219+
protected override Boolean userCanCreate( Sobject record )
220+
{
221+
return canCreate;
222+
}
223+
224+
protected override Boolean userCanUpdate( Sobject record )
225+
{
226+
return canUpdate;
227+
}
228+
229+
protected override Boolean userCanDelete( Sobject record )
230+
{
231+
return canDelete;
232+
}
233+
}
234+
235+
private inherited sharing class FAccount extends sfab_FabricatedSobject
236+
{
237+
public FAccount()
238+
{
239+
super( Account.class );
240+
name( 'Default Name' );
241+
}
242+
243+
public FAccount name( String name )
244+
{
245+
set( Account.Name, name );
246+
return this;
247+
}
248+
}
5249
}

0 commit comments

Comments
 (0)