+ Start a Discussion
DaveLDaveL 

unit tests suddenly failing: System.Runas customer portal user

I am ready to shoot myself.

On 1-25-2012 I was able to deploy changes to my prod org (and had been doing so for months).  On 1-30-2012, on attempting a minor deploy many old unit tests which hadn't been updated in months (one or two in a year) began to fail.  Of course everyone involved (including myself) claim to have made no (relevant) changes.

 

The interesting(?) thing is the failure mode.  In every case a row inserted into a custom object is NOT retrieved by an immediate SOQL select on that object WHEN done in a System.Runas block running as a Customer Portal User.

 

The SAME code runs without error in a Full Sandbox copy that is barely 30 days old.

I have checked / rechecked permissions/sharing rules/object security/all "known" changes, anything I can think of and can see no difference between the two orgs.  I created a new dev org from prod, and it also fails.  So, my assumption is that something changed in my prod org between the 25th and the 30th, but I have not been able to find what that might be.

 

I de-constructed the unit tests down to the following short example unit test which fails on Prod and my newly copied Dev org, but succeeds on my Full Sanbox (30 days old).  If I omit the System.runas, the test succeeds everywhere (however this is obviously not a solution as it invalidates the purpose of the testing).  The original failing tests were written under api 19, upgrading to 23 has had no effect.

 

 Something changed, but I'm at a loss for where else to look.

 

/**
 * 
 */
@isTest
private class RunAs {

    static testMethod void myUnitTest() {
        Contact con =new Contact();
        con.FirstName='FirstTestName';
        con.LastName ='LastTestName';
        con.Email='test@email.com';
        insert con;
       
        User user = new User();
        user.Username ='testemail@test.com';
        user.LastName = 'TestLastName';
        user.Email = 'TestEmail@test.com';
        user.alias = 'testAl';
        user.TimeZoneSidKey = 'America/New_York';
        user.LocaleSidKey = 'en_US';
        user.EmailEncodingKey = 'ISO-8859-1';
        user.ProfileId = [select id from Profile where Name='Student Portal User'].Id;
        user.LanguageLocaleKey = 'en_US';
        user.ContactId=con.Id;
        insert user;
        
        test.Starttest();  
        system.runAs(user){ 
	        Enrollment_Opportunity__c opp = new Enrollment_Opportunity__c(Applicant__c = con.id);
	        insert opp;
	        Enrollment_Opportunity__c readOpp = [select id from Enrollment_Opportunity__c limit 1];
        }
        test.stopTest();
    }
}

  On my prod org this fails on the select  for readOpp with "no rows for query".

  On my full sandbox this succeeds.

  The same results happen even if I assign the user's contact as the owner of the opp record.

   After changing to api 23 I added the seealldata=true to the @istest.  It had no effect.

 

   I clearly don't understand something.....any ideas are appreciated.

Best Answer chosen by Admin (Salesforce Developers) 
DaveLDaveL

So I have found my bug.  Its pretty mundane..but I learned a ton about Sharing rules in the process....so I guess it was worth it.

The actual problem was a workflow that an admin had updated which assigns owners.  And the vendor code I've inherited manages sharing, but manages it using the "Manual" Rowcause, thus making this User Managed sharing.

 

Whenever an owner changes, salesforce by design removes User Managed shares.

 

So my failure scenario was

1. Insert the object as one owner

2. triggers fire and add "Manual" shares

3. after the  insert,  workflows fire to change the owner

4. because the owner has changed the shares are removed

5. original owner can no longer see the row to retrieve, even though its directly after the insert.

 

Temp solution was to revisit the workflow, which wasn't doing what they wanted anyway.

Longer term I mean to revisit the sharing model that was implemented.

All Answers

kiranmutturukiranmutturu

1. its not required to insert an user in the test class where u can use instance of the object directly.

2. Enrollment_Opportunity__c is not having any records in production i think so thats y it is returning you no rows.. but ur test instance have some Enrollment_Opportunity__c object records...

 

3. as you are inserting the record in the test class it is not going to store this record in the object permanently. And when u query in the next line it slef this query considerers the organization data not the test data what u created... if your org is updated to spring12 then ur test class works with only test data not with org data.

DaveLDaveL

Hey, thanks for the comments. 

Our org is not yet Spring 12.

There SHOULD be Enrollment_Opportunity__c rows (at least one) because I just inserted one.  Thats how unit test data works, it should be visible for the duration of the test, and I have that work fine in many other unit tests. Thats the weird part, it only fails when done as part of a System.runas block using my portal user.  And btw, if I create a dummy custom object, it all works well too.  There is clearly some sharing issue with this specific object type, that I can't seem to locate.

 

And I do need to insert a user, because I need to test as a portal user and best practice dictates I don't assume there is one available to use.

 

d

kiranmutturukiranmutturu

i think u are not in sync with me please find the comments in red

/**
*
*/
@isTest
private class RunAs {

static testMethod void myUnitTest() {
Contact con =new Contact();
con.FirstName='FirstTestName';
con.LastName ='LastTestName';
con.Email='test@email.com';
insert con;

User user = new User();
user.Username ='testemail@test.com';
user.LastName = 'TestLastName';
user.Email = 'TestEmail@test.com';
user.alias = 'testAl';
user.TimeZoneSidKey = 'America/New_York';
user.LocaleSidKey = 'en_US';
user.EmailEncodingKey = 'ISO-8859-1';
user.ProfileId = [select id from Profile where Name='Student Portal User'].Id;
user.LanguageLocaleKey = 'en_US';
user.ContactId=con.Id;
//insert user;u can use the user with out inserting 

test.Starttest();
system.runAs(user){
Enrollment_Opportunity__c opp = new Enrollment_Opportunity__c(Applicant__c = con.id);
insert opp;
Enrollment_Opportunity__c readOpp = [select id from Enrollment_Opportunity__c limit 1];//at this level u will never get the test record because this will search the database record.. as test records are not commited to database...
}
test.stopTest();
}
}

 

DaveLDaveL

Ok, I now understand your point about the user object, although if its not inserted I don't understand how the system will reference it to make sharing rule decisions.

 

On the second point, if this were true no unit test that made their own data would ever work.  I have 50 unit tests that insert their own data, and then query it (all w/o ever committing it to the database after the test is over).  

 

And this works just fine if I use a different object than Enrollment_Opportunity__c, so I'm still inclined to believe I have a sharing issue somewhere.

craigmhcraigmh

This is a really weird quirk about test data. I've found that when you perform and insert, then try to query on that record, it won't be returned. Select queries only return real data. So instead of querying for the Enrollment_Opportunity__c record that you just created, continue to use the opp variable.

DaveLDaveL

@craigmh - thanks for the idea.  Problem is that there are many triggers that fire and update this object, and my testing (general, not this short example) needs to retrieve the data after the triggers have updated.

 

DaveLDaveL

So I have found my bug.  Its pretty mundane..but I learned a ton about Sharing rules in the process....so I guess it was worth it.

The actual problem was a workflow that an admin had updated which assigns owners.  And the vendor code I've inherited manages sharing, but manages it using the "Manual" Rowcause, thus making this User Managed sharing.

 

Whenever an owner changes, salesforce by design removes User Managed shares.

 

So my failure scenario was

1. Insert the object as one owner

2. triggers fire and add "Manual" shares

3. after the  insert,  workflows fire to change the owner

4. because the owner has changed the shares are removed

5. original owner can no longer see the row to retrieve, even though its directly after the insert.

 

Temp solution was to revisit the workflow, which wasn't doing what they wanted anyway.

Longer term I mean to revisit the sharing model that was implemented.

This was selected as the best answer