0

I finally have to ask some other folks about this one. There's a test class from an (awful) unmanaged package in our org, and it has been there for a year and never been a problem, but suddenly it is failing in production all the time, but passing in every sandbox that I can spin up.

All it does is test a simple class that calculates the number of contact roles on an opportunity. (Mind you the person who wrote it had no idea what they were doing, it generates wrong data any time more than one record is updated, ugh. I need to replace it anyway, but it's driving me nuts not understanding why it started failing in the first place.)

The class does not contain any reference to the organization it is running in, or any reference to any configuration settings. It is failing on a simple assert statement, but since it only fails in production, I cannot add any debug statements to it to figure it out. The class and test class are below.

It fails on this statement at the end of the test class:

system.assert(Oppty.Number_of_Contacts_Roles_Assigned__c == 2);

Again, I didn't write this and I don't need it to function better, I just really want to know what could be making it fail in a production environment that is identical to a sandbox in which it passes. We only have one other piece of code which impacts opportunity contact roles, and it is working fine, but I figured I would also include it at the bottom here in case there's something in it I'm not seeing.

Thanks for looking!

Trigger from unmanaged package:

trigger updatecontactrolecount on Opportunity (before insert, before update)
{

Boolean isPrimary;
Integer iCount;

Map<String, Opportunity> oppty_con = new Map<String, Opportunity>();//check if the contact role is needed and add it to the oppty_con map
for (Integer i = 0; i < Trigger.new.size(); i++) 
{
        oppty_con.put(Trigger.new[i].id,
        Trigger.new[i]);      
}
isPrimary = False; 
for (List<OpportunityContactRole> oppcntctrle :[select OpportunityId from OpportunityContactRole where (OpportunityContactRole.IsPrimary = True and OpportunityContactRole.OpportunityId in :oppty_con.keySet())])
{
 if (oppcntctrle .Size() >0)
 {
 isPrimary = True;     
 }
}
iCount = 0;
for (List<OpportunityContactRole> oppcntctrle2 : [select OpportunityId from OpportunityContactRole where (OpportunityContactRole.OpportunityId in :oppty_con.keySet())])//Query for Contact Roles
{    
 if (oppcntctrle2 .Size()>0)
 {
 iCount= oppcntctrle2 .Size();     
 }
}
for (Opportunity Oppty : system.trigger.new) //Check if  roles exist in the map or contact role isn't required 
{
Oppty.Number_of_Contacts_Roles_Assigned__c = iCount;
Oppty.Primary_Contact_Assigned__c =isPrimary; 
}
}

Test:

public class test_updatecontactrolecount
{
    public static testMethod void testcreateopptywithconditionandrole()
    {
        //Insert Opportunities 
        try
        {
            Opportunity Oppty = new Opportunity(Name='Oppty_test1',StageName='Stage 1 - Unqualified Prospect',Type ='New Business', CloseDate=System.Today());
            insert Oppty;

            // insert contact
            Contact[] cont = new Contact[]
            {
                new Contact(LastName = 'testcontact1'),
                new Contact(LastName = 'testcontact2')
            };    
            insert cont;    
            // insert contact role     
            OpportunityContactRole [] ocr = new OpportunityContactRole[]
            {
                new OpportunityContactRole(Role ='Business User',OpportunityId=Oppty.id ,Contactid = cont[0].id ,Isprimary = True),
                new OpportunityContactRole(Role ='Business User',OpportunityId=Oppty.id ,Contactid = cont[0].id ,Isprimary = False)
            };
            insert ocr;    
            Oppty.StageName = 'Stage 3 - Eval Request';    
            //Update opportunity

            Test.StartTest();
            update Oppty;
            Test.StopTest();

            Oppty =[SELECT Number_of_Contacts_Roles_Assigned__c,Primary_Contact_Assigned__c FROM Opportunity WHERE Id = :Oppty.Id];
            system.assert(Oppty.Number_of_Contacts_Roles_Assigned__c == 2);
            system.assert(Oppty.Primary_Contact_Assigned__c == True);
        }
        catch (System.DmlException e){}        
    } 
}

Only other code that reference Opp Contact Roles:

//This method creates contact roles on each new opportunity, when opp is created or moved to Pipeline, using all the contacts from the opportunity's account 
    public void CreateContactRoles(List<Opportunity> Opps, Map<ID, Opportunity> IDToOldOpp){

        Set<ID> AccountIDs = new Set<ID>();
        for(Opportunity Opp : Opps){                                  
                AccountIDs.add(Opp.AccountId);
        }

        //get all contacts for the accounts
        List<Contact> Cons = [SELECT ID, AccountID FROM Contact WHERE AccountID IN: AccountIDs];

        //create map of account ID to list of its contacts
        Map<Id, List<Contact>> AcctIDToContacts = new Map<ID,List<Contact>>();
            for(Contact C: Cons){
                if(AcctIDToContacts.get(C.AccountID) == null){
                   AcctIDToContacts.put(C.AccountID, new List<Contact>());
                }
                AcctIDToContacts.get(C.AccountID).add(C);
            }

        //get all existing OCRs from the opps
        List<OpportunityContactRole> ExistingOCRs = [SELECT OpportunityId, ContactID 
                                                     FROM OpportunityContactRole 
                                                     WHERE OpportunityId IN :new Map<Id,SObject>(Opps).keySet()];               

        //create map of opp ID to list of its OCRs        
        Map<ID, Map<ID, OpportunityContactRole>> OppIDToOCRs = new  Map<ID, Map<ID, OpportunityContactRole>>();
            for(OpportunityContactRole OCR: ExistingOCRs){
                if(OppIDToOCRs.get(OCR.OpportunityId) == null){
                      OppIDToOCRs.put(OCR.OpportunityId, new Map<ID, OpportunityContactRole>());
                }
                OppIDToOCRs.get(OCR.OpportunityId).put(OCR.ContactID, OCR);                                  
            }

            if(Test.isRunningTest() && Cons.size() >= 2){  // required to test the simultaneous insertion of opportunity and OCR that occurs when an opportunity is created from a contact record
                OpportunityContactRole TestOCR = new OpportunityContactRole(OpportunityId = Opps[0].Id, ContactId = Cons[0].ID);
                    OppIDToOCRs.put(Opps[0].Id, new map<ID, OpportunityContactRole>{Cons[1].ID => TestOCR});    
            }

        List<OpportunityContactRole> OCRstoUpsert = new List<OpportunityContactRole>();       

        //create or update the OpportunityContactRole records
        for(Opportunity Opp : Opps){            
            if(AcctIDToContacts.get(Opp.AccountId) != null){  //if the opportunity account has contacts
                for(Contact C: AcctIDToContacts.get(Opp.AccountID)){                   

                    String RecordTypeName = Schema.SObjectType.Opportunity.getRecordTypeInfosById().get(Opp.RecordTypeID).getname();

                    if(Trigger.IsInsert){  //create Decision Maker for SQLs, and all OCRs for Pipeline

                        if(RecordTypeName == 'SQL' || RecordTypeName == 'Pipeline'){

                            if(OppIDToOCRs.get(Opp.Id) != null && OppIDToOCRs.get(Opp.Id).get(C.Id) != null){  //if this contact is already an OCR (because it was created before insert), update it to Decision Maker
                                OpportunityContactRole OCRtoUpdate = OppIDToOCRs.get(Opp.Id).get(C.Id);                                  
                                    OCRtoUpdate.IsPrimary = true;
                                    OCRtoUpdate.Role = 'Decision Maker';
                                OCRstoUpsert.add(OCRtoUpdate);    
                            }                                                                                                    
                            else if (Opp.Created_for_Contact_ID__c == C.ID){  //otherwise, create DM if this contact is the Created_for_Contact
                                OpportunityContactRole OCR = new OpportunityContactRole(OpportunityId=Opp.ID, ContactId=C.ID);
                                    OCR.IsPrimary = true;
                                    OCR.Role = 'Decision Maker';
                                OCRstoUpsert.add(OCR);
                            }                       
                            else if(RecordTypeName == 'Pipeline'){  //otherwise, create OCR for this contact as Indirect Influence
                                OpportunityContactRole OCR = new OpportunityContactRole(OpportunityId=Opp.ID, ContactId=C.ID);
                                    OCR.Role='Indirect Influence';
                                OCRstoUpsert.add(OCR);
                            }
                        }
                    }

                    if(Trigger.IsUpdate){  //if Record Type changes to Opp, create all OCRs except Decision Maker, which was made on insert 

                        Opportunity OldOpp = IDToOldOpp.get(Opp.ID);
                        String RecordTypeNameOld = Schema.SObjectType.Opportunity.getRecordTypeInfosById().get(OldOpp.RecordTypeID).getname();

                        if(RecordTypeNameOld == 'SQL' && RecordTypeName == 'Pipeline' && 
                          (OppIDToOCRs.get(Opp.Id) == null || (OppIDToOCRs.get(Opp.Id) != null && OppIDToOCRs.get(Opp.Id).get(C.Id) == null))                           
                          ){
                                OpportunityContactRole OCR = new OpportunityContactRole(OpportunityId=Opp.ID, ContactId=C.ID);
                                    OCR.Role='Indirect Influence';  
                                OCRstoUpsert.add(OCR);
                        }
                    }                                                       
                }
            }
        }

        if (OCRstoUpsert.size() > 0) {                     
            try{
                upsert OCRstoUpsert; 
                    system.debug('>>>>>>>>OpportunityHandler.CreateContactRoles - Upserted '+OCRstoUpsert.size()+' OCRs after Opps were inserted or moved to Pipeline');
                        if(Test.isRunningTest()) {integer intTest =1/0;}
            }
            catch(Exception e){
                system.debug('>>>>>>>>OpportunityHandler.CreateContactRoles - Exception occurred trying to upsert OCRs after Opp was inserted or moved to Pipeline: '+e.getMessage());
                SystemLogUtil.ExceptionLog('OpportunityHandler.CreateContactRoles', e , 'Exception occurred trying to upsert OCRs after Opp was inserted or moved to Pipeline');
            }
        }
    }
  • 2
    You have two triggers on Opportunity, right? Can you establish via debug logs whether they are executing in the same order in prod versus sandbox? – David Reed May 30 at 4:35
  • You should at least get a message when you run the test that tells you the actual value of Oppty.Number_of_Contacts_Roles_Assigned__c - 0,1, or 3 or more. Is that a Roll-Up Summary field? – Thomas Taylor May 30 at 4:38
  • @ThomasTaylor no, it does not tell you the actual value unless you use system.assertEquals. And no it is not a roll up field, it is just a number field, as you can see the first trigger assigns it an integer. – number41 May 30 at 5:16
  • @DavidReed I had the same thought, and while I can't tell what order they are firing in, I can't see anything in the code that would cause it to make a difference. This is the worst part about not being able to add any debug statements. – number41 May 30 at 5:18
  • You could comment out the assert line on the test class so it passes so you can add a debug line? I know it's not ideal. @DavidReed's mention might be the issue. I once had an old test class that relied on the order of records returned in a SOQL query in an assert loop and it worked for years in Production and several sandboxes. It started failing in random sandboxes and was essentially roulette when we refreshed sandboxes whether the order of the records returned was different. In that case, you could spin up new dev sandboxes to see if you get one that starts to fail to debug. – Kris Goncalves May 30 at 21:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.