function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Kelly KKelly K 

INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY

Hi All,

 

I have a test class that was running fine until .  Now I continue to get this error message:

System.DmlException: Update failed. First exception on row 0 with id 006e0000003jL5JAAU; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: []

 

I've narrowed down the cause of the issue to our Sharing settings as opposed to a record Id or FLS issue - but I'm stuck there.

 

The sharing settings previously were:

- Private with access granted through heirarchies

- Owner in all internal users shared with all internal users.

 

The sharing were updated to

- Private with access granted through heirarchies

- Type = New Sale (Not Bundled), New Sale (Bundle) with All internal users

- Type = Net Organic with a specific group of roles

 

For the life of me, I cannot figure out why this code is failing with the new sharing model even though the Type field in the opportunity declaration of my test class is specified as "New Sale (Bundle)." The running user does have edit access on their profile and has edit access to the Vertical_Enrollment_Rep field. Just for kicks, if I switch it back to the original sharing settings, the test class passes.

 

 

Here is the code that's failing with the new sharing model:

   static testMethod void shouldCreateTaskOnUpdate() {
		
		Profile enrollmentProfile = [SELECT Id FROM Profile WHERE Name = 'Enrollment'];

		List<user> enrollmentUsers = [SELECT Id FROM User WHERE ProfileId = :enrollmentProfile.Id AND IsActive = TRUE];

		User enrollmentRep;
		
		for( User user : enrollmentUsers ) {

			if( enrollmentRep == null ) {
				enrollmentRep = user;
				continue;
			}
			
			break;
		}
		
		System.assertNotEquals(null, enrollmentRep);
		enrollmentRep = [SELECT Name, Profile.Name FROM User WHERE Id =:enrollmentRep.Id];
		System.debug('-------------------------------CURRENT USER NAME: ' + enrollmentRep.Name);
		System.debug('ENROLLMENT REP PASSED');

		System.debug('Insert Account');
    	Account vendorAccount = new Account(Name = 'Vendor Acct', Status__c = 'Vendor', Type__c = 'Vendor');
    	insert vendorAccount;
    	
    	Go_Forward_PM_System__c goForwardPM = new Go_Forward_PM_System__c (Name = 'Greenway', Status__c = 'Partner', Client_Type__c = 'Channel', 
    		Vendor_Account__c = vendorAccount.Id);
    	insert goForwardPM;
    	
       	Account account = new Account(Name='Test', Status__c='Backlog', Type='Medical Practice', Type__c = 'Medical Practice', Specialty__c = 'General Practice',
            	Go_Forward_PM_System__c = goForwardPM.Id, Number_of_Docs__c = 1, Leaving_Which_Clearinghouse__c = 'Cerner', Customer_Id__c = 'ZYDS3892', 
            	Current_Clearinghouse__c = 'Cerner');     
       	insert account;
		System.debug('Insert Account PASS');
    	
		Opportunity opportunity = new Opportunity ( AccountId = account.Id, Name = 'Test', StageName = 'Won', CloseDate = System.today(), Sales_Commit__c = 'Commit', Claims_Application__c = 'Yes', 
			Demo_Location__c = 'Onsite Demo', Our_Competition__c = 'Availity', Sr_Mgmt_involved_in_Demo__c = 'No', Client_Services_Select_One__c = 'Not Applicable', Type = 'New Sale (Bundle)', No_Enrollment_Needed__c = FALSE, 
			Vertical_Enrollment_Rep__c = null, Kick_Off_Complete_Enrollment__c = null, Vertical_Go_Live_Date__c = null, Never_Going_Live__c = null, Stalled_Vertical_Date__c = null);
		insert opportunity;

		Task[] tasks = [SELECT Id FROM Task WHERE AccountId =:account.Id and OwnerId = :enrollmentRep.id];

		System.assertEquals(0, tasks.size());		

		System.debug('----------------------Start Update!');
		System.runAs(enrollmentRep) {
			test.startTest();
				opportunity.Vertical_Enrollment_Rep__c = enrollmentRep.Id;
				update opportunity;
			test.stopTest();			
		}
		System.debug('-----------------------End Update');

		tasks = [SELECT Subject, ActivityDate, ReminderDateTime, IsReminderSet, RecordTypeId FROM Task WHERE AccountId =:account.Id and OwnerId =: enrollmentRep.Id];

		System.debug('Found tasks: ' + tasks);

		System.assertEquals(1, tasks.size());
		System.assertEquals('Vertical Kick Off Call: ' + account.Name, tasks[0].Subject);
		System.assertEquals(DateTime.newInstance(System.today().addDays(3), Time.newInstance(8, 0, 0, 0)),tasks[0].ReminderDateTime);
		System.assertEquals(True,tasks[0].IsReminderSet);
		System.assertEquals('01230000000cjHy', tasks[0].RecordTypeId);
    }

 

Help!

Best Answer chosen by Admin (Salesforce Developers) 
crop1645crop1645

Kelly

 

1. First of all, I want to thank you for your willingness to do debug statements to help investigate this.  It sure beats having to diagnose merely by Vulcan mind-meld which seems to be what some seem to expect

 

2. If the Opportunity is not found by the runAs() user, then it means that user does not have access to that record. This confirms why the update statement fails with the INSUFFICIENT_ACCESS_ON_CROSS... error as the running user can't update something it doesn't have access to

 

3. The asynchronous updating of sharing rules hypothesis would be unfortunate if true as it would make the system indeterminate.

 

4. Your debug logs identify which enrollment user is chosen in the testrun. Have you tried using that very same enrollment user as part of your UI test?

 

5. Ah ha -- I see this in the doc: "

  • You can’t use Apex to create criteria-based sharing rules. Also, criteria-based sharing cannot be tested using Apex."

6. So, here's my new hypothesis

 

* In the chain of execution, some APEX class is defined using 'with sharing' - this could be the test class itself

* Your OWD is private with hierarchies

* Your enrollment user is not above the sysad user (user that created the account+oppo) hence doesnt have OWD access to the testmethod oppo

* Apex can't test the criteria-based sharing you implemented

 

So, how to workaround?

1. Try defining the test class as 'without sharing' - verify that no other class that gets called in the chain uses the 'with sharing' (I don't think you have any other classes involved in this testmethod but I only know what I see posted)

 

2. If #1 doesn't work, your testmethod will need to create:

  * Two roles, role00 above role01

 * A sysad user with role01

 * An enrollment user with role00

 

In this way, the runas user will get private w/ hierarchy access to the Oppo created by the sysad user

 

I've had to use this approach in the past to test deletion triggers - where normally the runas user can't delete something they aren't the owner of or above in the role hierarchy - unless the runas user as modify all data - which normally they would not 

All Answers

crop1645crop1645

kkorynta

 

You didn't indicate which DML statement got the error (I'm assuming it is the update opportunity) but here are some things to think about:

 

1. Your testmethod creates Accounts using the running user but your system.runas() {} uses the enrollmentUser.id. Does that Id have access to the Account you created (that is the parent of the test Opportunity)?  Look at your Org Wide Defaults for Account.

2. Verify any other SObjects that get updated if the Opportunity is updated. Does the runAs user have access to those records per your sharing rules?

 

Kelly KKelly K

Hi Eric,

 

Yes it's an update DML statement - in particular after update. The insert DML statement passes fine, but this is because the 'enrollment user' inserts the opportunity, thus being the owner. Both the sharing model on the opportunity before and after gives read/write access as well.

 

Our Sharing model is pretty open for visibility - only exception is client cases and net organic opportunities. In our Sharing settings, for accounts we're set to Private, then opened to Owner in All Internal Users shared with All Internal Users. Sub information says read/write for account, contract, and asset, read/write for contacts, private for opportunities, and private for case.

 

As far as other sObjects updated by triggers for the opportunity, we have two that updates the account, one that adds contact roles on close & insert, one that copies the opp owner into another field, another that updates a couple of fields on the opp based on campaign history. This particular trigger is setting up a task for the enrollment rep to begin a KO call within 3 days whenever they're assigned to an opportunity.

 

Not sure if I mentioned it or not, but I manually replicate the test case and I don't run into any errors - as in I setup the account, vendor account, GFPM, and insert the opportunity as a sys admin then log in as an enrollment rep and update that field. I'm just not sure what I'm missing in the test class that is being affected by the sharing model. Any other thoughts?

 

crop1645crop1645

kkroynta

 

Good analysis; some other thoughts

 

1. Note: In your testmethod, the Opportunity is inserted by the running user, not the enrollment user as you state in the first paragraph in your last comment.  

2. Thus, if the OWD Sharing model is private for Opportunity, the enrollment user won't have access to the testmethod inserted Opportunity unless I am misunderstanding something

3. INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY usually means:

 

a) that one of the lookup fields on the updated opportunity references an SObject that the running user does not have permission to access -- or

b) the id value in the lookup field points at a record that has been deleted (I ran into this case once when my class was holding onto an id field after doing a Database.rollback(savepoint) statement, and trying to use that id later in the class.  

c) some logic error wherein a detail object has lost the lookup field to its parent 

 

I have a suspicion that the '[]' in the original error message is indicative of a null value in some required lookup field/parent field

 

Since you have other triggers happening on this opportunity or sobjects updated by the opportunity update (i.e. downstream updates), you may need additional debug statements before every DML operation 

 

 

Kelly KKelly K

Hi Eric,

 

Appreciate your thoughts. Here's what I'm confused about though: when I mannualy recreate the data in production in the same order as my test class (creating the account, GFPM, and opportunity as a system admin and then updating the opportunity as an enrollment rep), I don't get an error or insufficient privledges and the task is created as designed. What could be some reasons I would see this behavior? Also, if my sharing rule is criteria based, why would having a different opportunity owner affect their ability to edit if the opportunity meets that criteria?

 

I don't believe options b or c apply since the security model changed and not the test code. The test code was passing prior to the security model update.

 

I did run with the idea of deactivating all of the other triggers (for other objects) and commenting out the method calling other classes on the opportunity trigger and found that the error isn't a result of another trigger attempting to update.

 

Can you think of any other things I could check for?

crop1645crop1645

kkorynta

 

Remind me .. the testmethod passes in sandbox but not PROD?

 

1. One thing that is different between your manual test and your testmethod is that the testmethod doesn't fetch via SOQL the opportunity within the runAs {} block. Your manual test does do the SOQL. 

 

Add the SOQL into the testmethod.  

Are you sure there isn't an errant typo in the criteria-based sharing rule and the value of the testmethod's opportunity.type ?

 

If it works in sandbox but not prod, then there is something different between sandbox and prod

 

2. In the failing environment, does your enrollment user have read access to the Account in the testmethod? Does the user have write access to the Account (required if there are RSF fields on Account -> Oppo)

 

3. When you run the testmethod and examine the debug log, can you see how far into the sequence of events starting with update opportunity; before you get the exception?

* The before update trigger on this Oppo?

* Does the task get inserted?

* Any before/after insert triggers on the inserted Task?

* Any workflows based on the new task?

* Any after update triggers on the oppo? 

* Any workflows that execute upon the updated Opportunity?

 

3. Here are some other thoughts on what the error means:

http://boards.developerforce.com/t5/Salesforce-Labs-Open-Source/insufficient-access-rights-on-cross-reference-id/td-p/47296 

http://www.forcetree.com/2011/12/insufficientaccessoncrossreferenceentit.html

 

4. I presume you have looked at: 

http://www.salesforce.com/us/developer/docs/apexcode/index_Left.htm#StartTopic=Content/apex_classes_keywords_sharing.htm?SearchType=Stem  -- normally, Apex runs in system context and sharing is not enforced unless you use the with sharing keyword on some class

Kelly KKelly K

Hi Eric,

 

The test method fails in both environments. I found out about the issue during a major release, so for the time being I commented out that method so I could push my other changes into production. I found out about the OWDs after doing some digging when trying to go back and fix it.

 

1. Spelling errors - I checked and even rewrote that block and used the lookup glass to make sure I selected them appropriately. Also, if it was misspelled, I got an error that 'this is not a valid selection.'

 

2. In both environments, my enrollment user has read and write access to the account (OWDs take care of this). If it was a required field error, wouldn't the error returned from the test class repeat the validation rule/required field instead of a insufficient privs?

 

3. For this question, I tried to simplify this as much as possible by turning off all of my workflow rules that fire on the opportunity, account, and task and turning off all triggers except for my opportunity trigger, and then commented out the lines that invoke any other class than the one that I'm testing. Even with inactivating all of these, the test class still fails.

 

* The before update trigger on this Oppo? Turned it off. It's a straight copy of the Owner field into a user lookup field.

* Does the task get inserted? Manually, yes in both environments. In the test class, no.

* Any before/after insert triggers on the inserted Task? Turned those off too. Both of the ones on copy a value up to the contact/lead to fire out email alerts.

* Any workflows based on the new task? Turned these off as well. None of the WF rules on here apply to the 'client services' record type.

* Any after update triggers on the oppo? Turned those off, but yes we have a few.

* Any workflows that execute upon the updated Opportunity? Turned all those off, and yes, we have several, around 100.

 

As far as how far along in the debug log this gets, with everything inactivated, here's what the debug log reports for the few events just before it updates the opportunity:

10:27:01.977 (14977208000)|SOQL_EXECUTE_BEGIN|[183]|Aggregations:0|select Id from Task where (AccountId = :tmpVar1 and OwnerId = :tmpVar2)
10:27:01.998 (14998131000)|SOQL_EXECUTE_END|[183]|Rows:0
10:27:01.998 (14998159000)|HEAP_ALLOCATE|[183]|Bytes:4
10:27:01.998 (14998171000)|HEAP_ALLOCATE|[183]|Bytes:0
10:27:01.998 (14998265000)|HEAP_ALLOCATE|[183]|Bytes:4
10:27:01.998 (14998289000)|VARIABLE_SCOPE_BEGIN|[183]|tasks|LIST<Task>|true|false
10:27:01.998 (14998324000)|VARIABLE_ASSIGNMENT|[183]|tasks|{"serId":1,"value":[]}|0x6d782ed8
10:27:01.998 (14998333000)|STATEMENT_EXECUTE|[185]
10:27:01.998 (14998467000)|STATEMENT_EXECUTE|[187]
10:27:01.998 (14998561000)|VARIABLE_ASSIGNMENT|[187]|this.Vertical_Enrollment_Rep__c|"00530000001cUOCAA2"|0xa8a7a5
10:27:01.998 (14998570000)|STATEMENT_EXECUTE|[189]
10:27:01.998 (14998578000)|HEAP_ALLOCATE|[189]|Bytes:35
10:27:01.998 (14998612000)|USER_DEBUG|[189]|DEBUG|----------------------Start Update!
10:27:02.082 (15082195000)|HEAP_ALLOCATE|[190]|Bytes:8
10:27:02.082 (15082221000)|STATEMENT_EXECUTE|[190]
10:27:02.082 (15082228000)|STATEMENT_EXECUTE|[191]
10:27:02.083 (15083299000)|STATEMENT_EXECUTE|[192]
10:27:02.083 (15083336000)|HEAP_ALLOCATE|[192]|Bytes:8
10:27:02.083 (15083354000)|DML_BEGIN|[192]|Op:Update|Type:Opportunity|Rows:1
10:27:02.083 (15083374000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:8
10:27:02.403 (15403582000)|DML_END|[192]
10:27:02.403 (15403689000)|EXCEPTION_THROWN|[192]|System.DmlException: Update failed. First exception on row 0 with id 006e0000003ligDAAQ; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: []
10:27:02.405 (15405192000)|HEAP_ALLOCATE|[192]|Bytes:184
10:27:02.607 (15607886000)|FATAL_ERROR|System.DmlException: Update failed. First exception on row 0 with id 006e0000003ligDAAQ; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: []

Class.OpportunityVerticalKOCallEnrollmentTest.shouldCreateTaskOnUpdate: line 192, column 1
10:27:02.607 (15607917000)|FATAL_ERROR|System.DmlException: Update failed. First exception on row 0 with id 006e0000003ligDAAQ; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: []

Class.OpportunityVerticalKOCallEnrollmentTest.shouldCreateTaskOnUpdate: line 192, column 1
10:26:56.205 (15607941000)|CUMULATIVE_LIMIT_USAGE

 

4. Took a read at both of those articles yesterday, both of those great articles.

 

For the first one, my enrollment user has the same access to the same RecordTypes (on Account, Opportunity, and Task) in production and the sandbox.

 

For the second article, I thought it might have fallen into scenario 1, as this class is creating a task based on a field (enrollment user) being filled in. Typically the process is our data entry team fills this out, which creates a task for an enrollment rep. Data entry is located above the enrollment rep in the company role hierarchy. However, the way I put the test together, the running user is the enrollment rep, so they're assigning the task to themselves.

 

I thought about scenario 2 as well. I do know that if I have the enrollment rep create the account in my run as block, then the class passes. 99% of the time, the enrollment user will not be creating or owning accounts. They have edit access. Our sharing model for accounts did not change - it remains as "Owner in all internal users shared with all internal users." Manual testing confirms that they have read/edit access to both the account and opportunity that they do not own.

 

5. I did throw in the without sharing keyword on my class just to be sure that it was running w/o regards to the OWD. Test class still fails with it.

 

I have a meeting later today with our development consultant and I'm hoping he can get an inside look at everything and see if he can coach me through it. I'll post w/e the solution was if we figure one out. I appreciate all your time looking at this and sharing your wisdom. 

 

- Kelly

 

Here's the class and the trigger (no commented out code) if you were interested in looking at it. 

public without sharing class OpportunityVerticalKOCallEnrollmentTask {

	public static void createKOCallEnrollmentTask(List<Opportunity> opportunities) {
		if(TriggerHandler.firstRunOpportunityVerticalKOCallEnrollmentTask) {
			Task[] tasks = new Task[] {};
			Id currentUserId = UserInfo.getUserId();
			String currentUserProfileId = UserInfo.getProfileId();
			Date taskDate = System.today().addDays(3);
			List<Opportunity> opportunitiesForTaskCreation = new List<Opportunity>();
		
			RecordType recordType = RecordTypeResolver.find('Task', 'Client Services');
		
			//Exclude all System Admins from creating this task
			if (new Set<Id> {'00e30000000dSKN'}.contains(currentUserProfileId))
				return;
			
			for (Opportunity opportunity : opportunities) {
				if (opportunity.Vertical_Enrollment_Rep__c != null &&
				 	opportunity.Kick_Off_Complete_Enrollment__c == null &&
				 	opportunity.Vertical_Go_Live_Date__c == null &&
				 	opportunity.Never_Going_Live__c == null) {		
					
					opportunitiesForTaskCreation.add(opportunity);
				}
			}
			
			if (opportunitiesForTaskCreation != null) {
				for (Opportunity opportunity : [SELECT AccountId, Account.Name, Vertical_Enrollment_Rep__c FROM Opportunity WHERE Id IN :opportunitiesForTaskCreation]) {
					
					tasks.add( new Task (Subject = 'Vertical Kick Off Call: ' + (opportunity.AccountId != null ? opportunity.Account.Name : '<Unknown>'),
									 	 OwnerId = opportunity.Vertical_Enrollment_Rep__c,
									 	 ActivityDate = taskDate,
									 	 WhatId = opportunity.Id,
									 	 ReminderDateTime = DateTime.newInstance(taskDate, Time.newInstance(8, 0, 0,0)),
									 	 IsReminderSet = True,
									 	 RecordTypeId = recordType.Id));			
				}
		
				insert tasks;
			}		
		}
		TriggerHandler.firstRunOpportunityVerticalKOCallEnrollmentTask = false;
	}
}

 

trigger OpportunityTrigger on Opportunity (after insert, after update, before insert, before update) {
	
	//Run Before triggers
	if (trigger.isBefore) {
		if (trigger.isInsert || trigger.isUpdate) {
			List<Opportunity> opportunities = new List<Opportunity>();
			Set<Id> opportunityIds = new Set<Id>();
			
			for (Opportunity opportunity : Trigger.new) { 
				opportunities.add(opportunity);
				opportunityIds.add(opportunity.Id);
			}
			
			try { 	
				OpportunityOwnerFullName.setOwnerFullName(opportunities);
			}
			catch (System.DMLException e) {
				for (Opportunity opportunity : opportunities) 
					opportunity.adderror(e.getMessage());
			}
			catch (Exception e) {
				for (Opportunity opportunity : opportunities) 
					opportunity.adderror(e.getMessage());			
			}
		}
	}
	
	//Run After triggers
	else if (trigger.isAfter) {

		List<Opportunity> opportunities = new List<Opportunity>();
		List<Opportunity> oppsInsertClosed = new List<Opportunity>();
		Set<Id> opportunityIds = new Set<Id>();

		for (Opportunity opportunity : Trigger.new) { 
			opportunities.add(opportunity);		
			opportunityIds.add(opportunity.Id);
			
			if(opportunity.IsClosed) 
				oppsInsertClosed.add(opportunity);
		}	
		
		//On After Insert 
		if(trigger.isInsert) {
			try { 
				if(opportunities != null) {
					OpportunityVerticalKOCallEnrollmentTask.createKOCallEnrollmentTask(opportunities);
					OpportunityVerticalKOCallTrainerTask.createVerticalKOCallTrainerTask(opportunities);					
				}
					
				if(opportunityIds != null) {
					OpportunityAddContactRoles.AddToOpportunities(opportunityIds);
					OpportunityPrimaryCampaignSource.setPrimaryCampaignSource(opportunityIds); 
					OpportunityPrimaryCampaignSource.setLeadSourceAndMediaType(opportunityIds);	
					OpportunityAccountStatusFromOpportunity.setAccountStatus(opportunityIds);
				}

				if(oppsInsertClosed != null)
					OpportunityAttritionUpdatePMandCH.updatePMandCHonAccount(oppsInsertClosed);				
			}
			catch (System.DMLException e) {
				for (Opportunity opportunity : opportunities) 
					opportunity.adderror(e.getMessage());
			}
			catch (Exception e) {
				for (Opportunity opportunity : opportunities) 
					opportunity.adderror(e.getMessage());			
			}
		}	
		
		//On After Update	
		else if (trigger.isUpdate) {
			Set<Id> oppIdsOnClose = new Set<Id>();	
			List<Opportunity> oppsOnClose = new List<Opportunity>();
			List<Opportunity> enrollmentTaskOpps = new List<Opportunity>();
			List<Opportunity> trainerTaskOpps = new List<Opportunity>();
				
			for (Opportunity opportunity : opportunities){
				//Closed Opportunities
				if (Trigger.oldMap.get(opportunity.Id).IsClosed == false && Trigger.newMap.get(opportunity.Id).IsClosed == true) {
					oppsOnClose.add(opportunity);
					oppIdsOnClose.add(opportunity.Id);
				}
				
				//Enrollment Rep Updated
				if (Trigger.oldMap.get(opportunity.Id).Vertical_Enrollment_Rep__c  != Trigger.newMap.get(opportunity.Id).Vertical_Enrollment_Rep__c ) 
					enrollmentTaskOpps.add(opportunity);
					
				//Trainer Rep Updated
				if ((Trigger.oldMap.get(opportunity.Id).Enrollment_Completed__c == null || Trigger.oldMap.get(opportunity.Id).No_Enrollment_Needed__c == false) &&
					(Trigger.newMap.get(opportunity.Id).Enrollment_Completed__c != null || Trigger.newMap.get(opportunity.Id).No_Enrollment_Needed__c == true))
					trainerTaskOpps.add(opportunity);
			}
			
			try { 
				if(oppsOnClose != null)
					OpportunityAttritionUpdatePMandCH.updatePMandCHonAccount(oppsOnClose);
						
				if(oppIdsOnClose != null) {
					if(TriggerHandler.firstRunOpportunityAddContactRoles) {
						OpportunityAddContactRoles.AddToOpportunities(oppIdsOnClose);
						TriggerHandler.firstRunOpportunityAddContactRoles = false;
					}
										
					OpportunityPrimaryCampaignSource.setPrimaryCampaignSource(oppIdsOnClose);
					OpportunityPrimaryCampaignSource.setLeadSourceAndMediaType(oppIdsOnClose);
				}
				
				if(opportunityIds != null) 	
					OpportunityAccountStatusFromOpportunity.setAccountStatus(opportunityIds);	
				
				if(enrollmentTaskOpps != null) 
					OpportunityVerticalKOCallEnrollmentTask.createKOCallEnrollmentTask(enrollmentTaskOpps);
				
				if(trainerTaskOpps != null)
					OpportunityVerticalKOCallTrainerTask.createVerticalKOCallTrainerTask(trainerTaskOpps);
			
			}
			catch (System.DMLException e) {
				for (Opportunity opportunity : opportunities) 
					opportunity.adderror(e.getMessage());
			}
			catch (Exception e) {
				for (Opportunity opportunity : opportunities) 
					opportunity.adderror(e.getMessage());				
			}			
		}
	}
}

 

 

 

 

crop1645crop1645

Kelly

 

If you disabled all the triggers and workflows, then the debug log fails pretty early on in the DML operation - all we see is a Heap allocate of 8 bytes.

 

1. Did you add to the testmethod within the runAs(..) {..} SOQL selects for

 * the opportunity 

 * the account

 * any other records that opportunity has a lookup field relationship to

 

If you use 'for update' in the SOQL, this will help to in locating the issue

 

2. Note that one notable difference in testmethods from doing user interface testing is that testmethods defined at V24.0 or higher do not have access to any org data unless the testmethod as annotation @isTest(seeAlldata=true).  

 

If not present, add this annotation to the testmethod and try again

Kelly KKelly K

Hi Eric,

 

I added @isTest(seeAlldata=true) and no luck on that. The consultant I'm working with is just as stumped as I am, but has a suspicion that it has something to do with the sharing rule calculating asyncrously.

 

Could you elaborate a little by what you mean on adding a SOQL select? I'm not sure I understand, but I gave it a try. Is this what you mean?

		System.debug('----------------------Start Update!');
		System.runAs(enrollmentRep) {  
			test.startTest();
				system.debug('-------------This is the account Id ' + account.Id);			
				system.debug('-------------This is the opportunity Id ' + opportunity.Id);
				account = [SELECT Id, Name FROM Account WHERE Id =: account.Id];
				opportunity = [SELECT Id, AccountId, Name, StageName, CloseDate, Sales_Commit__c, Claims_Application__c, Demo_Location__c, Our_Competition__c, Sr_Mgmt_involved_in_Demo__c,
									Client_Services_Select_One__c, Type, RecordTypeId, Vertical_Enrollment_Rep__c
							   FROM Opportunity WHERE Id =: opportunity.Id];				
				opportunity.Vertical_Enrollment_Rep__c = enrollmentRep.Id;							   							   
				update opportunity;
				
			test.stopTest();			
		}
		System.debug('-----------------------End Update');

 If so, this is the error message I get:

 

09:36:11.014 (16014012000)|USER_DEBUG|[192]|DEBUG|----------------------Start Update!
09:36:11.306 (16306080000)|HEAP_ALLOCATE|[193]|Bytes:8
09:36:11.306 (16306116000)|STATEMENT_EXECUTE|[193]
09:36:11.306 (16306120000)|STATEMENT_EXECUTE|[194]
09:36:11.307 (16307480000)|STATEMENT_EXECUTE|[195]
09:36:11.307 (16307497000)|HEAP_ALLOCATE|[195]|Bytes:36
09:36:11.307 (16307553000)|HEAP_ALLOCATE|[195]|Bytes:18
09:36:11.307 (16307567000)|HEAP_ALLOCATE|[195]|Bytes:54
09:36:11.307 (16307583000)|USER_DEBUG|[195]|DEBUG|-------------This is the account Id 001e000000Akz3RAAR
09:36:11.307 (16307593000)|STATEMENT_EXECUTE|[196]
09:36:11.307 (16307600000)|HEAP_ALLOCATE|[196]|Bytes:40
09:36:11.307 (16307623000)|HEAP_ALLOCATE|[196]|Bytes:18
09:36:11.307 (16307633000)|HEAP_ALLOCATE|[196]|Bytes:58
09:36:11.307 (16307644000)|USER_DEBUG|[196]|DEBUG|-------------This is the opportunity Id 006e0000004Aw5aAAC
09:36:11.307 (16307652000)|STATEMENT_EXECUTE|[197]
09:36:11.307 (16307657000)|HEAP_ALLOCATE|[197]|Bytes:48
09:36:11.307 (16307670000)|HEAP_ALLOCATE|[197]|Bytes:4
09:36:11.307 (16307926000)|SOQL_EXECUTE_BEGIN|[197]|Aggregations:0|select Id, Name from Account where Id = :tmpVar1
09:36:11.458 (16458374000)|SOQL_EXECUTE_END|[197]|Rows:1
09:36:11.458 (16458403000)|HEAP_ALLOCATE|[197]|Bytes:8
09:36:11.458 (16458422000)|HEAP_ALLOCATE|[197]|Bytes:41
09:36:11.458 (16458449000)|HEAP_ALLOCATE|[197]|Bytes:8
09:36:11.458 (16458470000)|HEAP_ALLOCATE|[197]|Bytes:33
09:36:11.458 (16458524000)|HEAP_ALLOCATE|[197]|Bytes:12
09:36:11.458 (16458576000)|VARIABLE_ASSIGNMENT|[197]|account|{"serId":1,"value":{"Name":"Test","Id":"001e000000Akz3RAAR"}}|0x4c8c55a8
09:36:11.458 (16458586000)|STATEMENT_EXECUTE|[198]
09:36:11.458 (16458593000)|HEAP_ALLOCATE|[198]|Bytes:271
09:36:11.458 (16458605000)|HEAP_ALLOCATE|[198]|Bytes:4
09:36:11.458 (16458972000)|SOQL_EXECUTE_BEGIN|[198]|Aggregations:0|select Id, AccountId, Name, StageName, CloseDate, Sales_Commit__c, Claims_Application__c, Demo_Location__c, Our_Competition__c, Sr_Mgmt_involved_in_Demo__c, Client_Services_Select_One__c, Type, RecordTypeId, Vertical_Enrollment_Rep__c from Opportunity where Id = :tmpVar1
09:36:11.663 (16663651000)|SOQL_EXECUTE_END|[198]|Rows:0
09:36:11.663 (16663681000)|HEAP_ALLOCATE|[198]|Bytes:4
09:36:11.663 (16663694000)|HEAP_ALLOCATE|[198]|Bytes:0
09:36:11.663 (16663718000)|HEAP_ALLOCATE|[198]|Bytes:4
09:36:11.663 (16663738000)|HEAP_ALLOCATE|[198]|Bytes:37
09:36:11.663 (16663830000)|HEAP_ALLOCATE|[198]|Bytes:46
09:36:11.821 (16821935000)|FATAL_ERROR|System.QueryException: List has no rows for assignment to SObject

Class.OpportunityVerticalKOCallEnrollmentTest.shouldCreateTaskOnUpdate: line 198, column 1
09:36:11.821 (16821973000)|FATAL_ERROR|System.QueryException: List has no rows for assignment to SObject

Class.OpportunityVerticalKOCallEnrollmentTest.shouldCreateTaskOnUpdate: line 198, column 1
09:36:05.695 (16821999000)|CUMULATIVE_LIMIT_USAGE
09:36:05.695|LIMIT_USAGE_FOR_NS|(default)|

 

I don't understand why the SOQL is returning 0 rows when I give it the Id?

 

 

crop1645crop1645

Kelly

 

1. First of all, I want to thank you for your willingness to do debug statements to help investigate this.  It sure beats having to diagnose merely by Vulcan mind-meld which seems to be what some seem to expect

 

2. If the Opportunity is not found by the runAs() user, then it means that user does not have access to that record. This confirms why the update statement fails with the INSUFFICIENT_ACCESS_ON_CROSS... error as the running user can't update something it doesn't have access to

 

3. The asynchronous updating of sharing rules hypothesis would be unfortunate if true as it would make the system indeterminate.

 

4. Your debug logs identify which enrollment user is chosen in the testrun. Have you tried using that very same enrollment user as part of your UI test?

 

5. Ah ha -- I see this in the doc: "

  • You can’t use Apex to create criteria-based sharing rules. Also, criteria-based sharing cannot be tested using Apex."

6. So, here's my new hypothesis

 

* In the chain of execution, some APEX class is defined using 'with sharing' - this could be the test class itself

* Your OWD is private with hierarchies

* Your enrollment user is not above the sysad user (user that created the account+oppo) hence doesnt have OWD access to the testmethod oppo

* Apex can't test the criteria-based sharing you implemented

 

So, how to workaround?

1. Try defining the test class as 'without sharing' - verify that no other class that gets called in the chain uses the 'with sharing' (I don't think you have any other classes involved in this testmethod but I only know what I see posted)

 

2. If #1 doesn't work, your testmethod will need to create:

  * Two roles, role00 above role01

 * A sysad user with role01

 * An enrollment user with role00

 

In this way, the runas user will get private w/ hierarchy access to the Oppo created by the sysad user

 

I've had to use this approach in the past to test deletion triggers - where normally the runas user can't delete something they aren't the owner of or above in the role hierarchy - unless the runas user as modify all data - which normally they would not 

This was selected as the best answer
Kelly KKelly K

1. You're welcome. 

2. Agreed

3. Agreed

4. I did, manual UI test I used a sys admin to create the vendor account, gfpm, account, opportunity, then logged in as my enrollment rep and updated the opportunity. No problems to be had there.

 

5. http://na13.salesforce.com/help/doc/en/security_sharing_cbs_about.htm .... that stinks, but it does get to the bottom of it.

 

6. Part 1 - tried this, didn't work.

    Part 2 - Essentially, when testing apex, you have to completely ignore any criteria based sharing rules that are in place because apex tests doesn't use them? IMO, this seems a little impractical and unfortunate, but definitely a great thing to know....

 

I think, for the sake of not trying to create several new roles/users in a test class (which new users will count against your license limits during your test - found that out a while back when we didn't have enough licenses at the time) and to try and keep the test class closer to our business process, what I'll do is keep the order of DML statements the same, but before the run as statement, set up a manual share statement since the criteria based sharing will not run.

 

So I modified that section of my test code to look something like this. And... it passes. 

 

		Opportunity opportunity = new Opportunity ( AccountId = account.Id, Name = 'Test', StageName = 'Won', CloseDate = System.today(), Sales_Commit__c = 'Commit', Claims_Application__c = 'Yes', 
			Demo_Location__c = 'Onsite Demo', Our_Competition__c = 'Availity', Sr_Mgmt_involved_in_Demo__c = 'No', Client_Services_Select_One__c = 'Not Applicable', Type = 'New Sale (Bundle)',
			RecordTypeId = '01230000000Jcn2'); 
		insert opportunity;
		
		system.debug('---------------Opportunity Inserted' + opportunity.Id);
		
		OpportunityShare sysAdToEnrollShare = new OpportunityShare(OpportunityId = opportunity.Id, OpportunityAccessLevel = 'Edit', UserOrGroupId = enrollmentRep.Id);
		insert sysAdToEnrollShare;

		Task[] tasks = [SELECT Id FROM Task WHERE AccountId =:account.Id and OwnerId = :enrollmentRep.id];

		System.assertEquals(0, tasks.size());		

		System.debug('----------------------Start Update!');
		System.runAs(enrollmentRep) {  
			test.startTest();
				system.debug('-------------This is the account Id ' + account.Id);			
				system.debug('-------------This is the opportunity Id ' + opportunity.Id);
				account = [SELECT Id, Name FROM Account WHERE Id =: account.Id];
				opportunity = [SELECT Id, AccountId, Name, StageName, CloseDate, Sales_Commit__c, Claims_Application__c, Demo_Location__c, Our_Competition__c, Sr_Mgmt_involved_in_Demo__c,
									Client_Services_Select_One__c, Type, RecordTypeId, Vertical_Enrollment_Rep__c
							   FROM Opportunity WHERE Id =: opportunity.Id];				
				opportunity.Vertical_Enrollment_Rep__c = enrollmentRep.Id;							   							   
				update opportunity;
				
			test.stopTest();			
		}
		System.debug('-----------------------End Update');

 

While I knew I could Jimmy it to get it to work, I just didn't understand why it didn't work the way it was. But now that I know... I know for all future test code involving critieria based sharing.

 

Really appreciate your help Eric, Kudos. 

 

crop1645crop1645

Kelly

 

no problem.. INSUFFICIENT_ACCESS_ON... error is one of the hardest to diagnose so in working through your scenario, I learned something myself.

 

The manual share is a great idea too and simpler than my workaround#2; come to think of it, I may have used this before when I had testmethods failing due to a private OWD with criteria-based sharing