2

On a test class for a class that handles lead conversion via apex, 6 out of 7 methods (the ones that convert the lead) are failing in production. However, I can run the test in a fresh sandbox and it passes.

There are no callouts on the test class or the class that does the lead conversion, and I cannot figure out where the callout is happening. The stack trace on the error is empty:

Methods defined as TestMethod do not support Web service callouts
Stack Trace: null

I do have an invocable method on opportunities that does a callout to set exchange rate. It should not be triggered for the type of opportunity created by the lead conversion, and I tried deactivating the process that fires the callout but still got the error when running the test in production.

Are there any known differences in this behavior between sandbox and production? Any suggestions on how to figure out what callout is being called? I literally refreshed the sandbox today and all test methods pass in sandbox.

I will paste the full test and class in below in case it helps. Thanks.

Test Class

@isTest
private class ConvertLeadControllerTests {
    
    @isTest static void TestConvertLead() {
        PageReference ref = Page.ConvertLead;
        Test.setCurrentPage(ref);        
        Lead myLead = GetLead();
        insert myLead;
        system.debug('mylead id = ' +mylead.Id);
        ApexPages.CurrentPage().getParameters().put('id', myLead.Id);
        ApexPages.CurrentPage().getParameters().put('status', EncodingUtil.urlEncode('1st Op - Qualified for New Idea Call', 'UTF-8'));
        ConvertLeadController cl = new ConvertLeadController();
        cl.ConvertLead();
        Account acc =[Select Id From Account Where Name=:myLead.Company Limit 1];
        system.debug('acc id = ' + acc.id);
        Opportunity opp = [Select Id From Opportunity Where AccountId =:acc.Id];
        System.assert(acc != null);
        System.assert(opp != null);
    }
    
    @isTest static void TestConvertLeadWithExistingContactAndAccount() {
        PageReference ref = Page.ConvertLead;
        Test.setCurrentPage(ref);
        Account acc = new Account();
        acc.Name = 'TestOrg';
        insert acc;
        Contact con = new Contact();
        con.LastName = 'TestLast';
        con.FirstName = 'TestFirst';
        con.AccountId = acc.Id;
        con.Email = 'test@test.com';
        insert con;
        Lead myLead = GetLead();
        insert myLead;
        ApexPages.CurrentPage().getParameters().put('id', myLead.Id);
        ApexPages.CurrentPage().getParameters().put('status', EncodingUtil.urlEncode('1st Op - Qualified for New Idea Call', 'UTF-8'));
        ConvertLeadController cl = new ConvertLeadController();
        cl.ConvertLead();
        PageReference pgRef = cl.convertLead2();
        Opportunity opp = [Select Id From Opportunity Where AccountId =:acc.Id];
        OpportunityContactRole ocr = [Select Id from OpportunityContactRole where ContactId=:con.Id];
        system.assert(ocr != null);
        System.assert(opp != null);
    }
    
    @isTest static void TestConvertLeadWithExistingAccount() {
        PageReference ref = Page.ConvertLead;
        Test.setCurrentPage(ref);
        Account acc = new Account();
        acc.Name = 'TestOrg';
        insert acc;
        Lead myLead = GetLead();
        insert myLead;
        ApexPages.CurrentPage().getParameters().put('id', myLead.Id);
        ApexPages.CurrentPage().getParameters().put('status', EncodingUtil.urlEncode('1st Op - Qualified for New Idea Call', 'UTF-8'));
        ConvertLeadController cl = new ConvertLeadController();
        cl.ConvertLead();
        PageReference pgRef = cl.convertLead2();
        Opportunity opp = [Select Id From Opportunity Where AccountId =:acc.Id];
        System.assert(opp != null);
    }
    
    @isTest static void TestConvertLeadWithExistingContact() {
        PageReference ref = Page.ConvertLead;
        Test.setCurrentPage(ref);
        Account acc = new Account();
        acc.Name = 'TestOrg1';
        insert acc;
        Contact con = new Contact();
        con.LastName = 'TestLast';
        con.FirstName = 'TestFirst';
        con.AccountId = acc.Id;
        con.Email = 'convertLeadControllerTests123@convertLeadControllerTests.com';
        insert con;
        Lead myLead = GetLead();
        mylead.Email = 'convertLeadControllerTests123@convertLeadControllerTests.com';
        insert myLead;
        ApexPages.CurrentPage().getParameters().put('id', myLead.Id);
        ApexPages.CurrentPage().getParameters().put('status', EncodingUtil.urlEncode('1st Op - Qualified for New Idea Call', 'UTF-8'));
        ConvertLeadController cl = new ConvertLeadController();
        cl.ConvertLead();
        PageReference pgRef = cl.convertLead2();
        OpportunityContactRole ocr = [Select Id from OpportunityContactRole where ContactId=:con.Id];
        system.assert(ocr != null);
    }
    
    @isTest static void TestConvertLeadEmailMatchWithExistingContact() {
        PageReference ref = Page.ConvertLead;
        Test.setCurrentPage(ref);
        Account acc = new Account();
        acc.Name = 'TestOrg1';
        insert acc;
        Contact con = new Contact();
        con.LastName = 'Con TestLast';
        con.FirstName = 'Last TestFirst';
        con.AccountId = acc.Id;
        con.Email = 'convertLeadControllerTests12@convertLeadControllerTests.com';
        insert con;
        Lead myLead = GetLead();
        mylead.Email = 'convertLeadControllerTests12@convertLeadControllerTests.com';
        myLead.Nominator__c = con.Id;
        myLead.Reference_1__c = con.Id;
        myLead.Reference_2__c = con.Id;
        myLead.Reference_3__c = con.Id;
        insert myLead;
        ApexPages.CurrentPage().getParameters().put('id', myLead.Id);
        ApexPages.CurrentPage().getParameters().put('status', EncodingUtil.urlEncode('1st Op - Qualified for New Idea Call', 'UTF-8'));
        ConvertLeadController cl = new ConvertLeadController();
        cl.ConvertLead();
        PageReference pgRef = cl.convertLead2();
        OpportunityContactRole ocr = [Select Id from OpportunityContactRole where ContactId=:con.Id LIMIT 1];
        system.assert(ocr != null);
    }
    
    //Assigns the lead to the queue before invoking the conversion logic.
    @isTest static void TestConvertLeadFromQueue() {
        Group testGroup = new Group(Name='TestGroup', Type='Queue');
        insert testGroup;
        
        System.runAs(new User(Id=UserInfo.getUserId()))
        {   
            QueuesObject testQueue = new QueueSObject(QueueID = testGroup.id, SObjectType = 'Lead');
            insert testQueue;
        }
        
        Lead myLead = GetLead();
        myLead.OwnerId = testGroup.Id;
        
        insert myLead;
        ApexPages.CurrentPage().getParameters().put('id', myLead.Id);
        ApexPages.CurrentPage().getParameters().put('status', EncodingUtil.urlEncode('1st Op - Qualified for New Idea Call', 'UTF-8'));
        ConvertLeadController cl = new ConvertLeadController();
        cl.ConvertLead();
        Account acc =[Select Id From Account Where Name=:myLead.Company Limit 1];
        Opportunity opp = [Select Id From Opportunity Where AccountId =:acc.Id];
        System.assert(opp != null);
        
    }
    
    @isTest static void TestCancel() {
        PageReference ref = Page.ConvertLead;
        Test.setCurrentPage(ref);
        ApexPages.CurrentPage().getParameters().put('id', 'aaaaaaaaaaaaaaa');
        ConvertLeadController cl = new ConvertLeadController();
        cl.Cancel();
    }
    
    private static Lead GetLead(){
        //+need to randomize the data created by this test
        //contact and org name also need to match, so need to use testsetup and query for them
        //Integer randomInt = Integer.valueOf(math.rint(math.random()*1000000));
        Lead myLead = new Lead();
        myLead.FirstName = 'TestFirst';
        myLead.LastName = 'TestLast';
        myLead.Company = 'TestOrg';
        myLead.Status = 'Nomination - First Review needed';
        myLead.Country_of_Election__c = 'United States';
        myLead.Country_Of_Social_Impact__c ='India';
        myLead.Diamond_of_Social_Impact__c = 'Asia';
        myLead.Cluster__c = 'Aging';
        myLead.Country = 'India';
        myLead.Organization_Type__c ='Bank';
        myLead.Address_Type__c ='Home';
        myLead.State ='Karnataka';
        myLead.Street ='Bangalore';
        myLead.Target_Population__c ='Business';
        myLead.Gender__c ='Male';
        mylead.New_Idea__c ='New Idea';
        myLead.Opportunity_Type__c ='Grant';
        myLead.Email ='test@test.com';
        myLead.Application_Nomination_Attached__c = true;
        //myLead.Application_Nomination_Completed__c = true;
        myLead.City ='Bangalore';
        myLead.Fellow_Type__c ='Global';
        myLead.Industry ='Banking';
        mylead.Interview_Completed__c =true;
        myLead.Phone ='99999999';
        myLead.Reference_Check_Completed__c = true;
        myLead.Site_Visit_Completed__c = true;
        myLead.Preferred_Language__c ='English';
        myLead.Research_Completed__c = true;
        myLead.RecordTypeId = Schema.SObjectType.Lead.getRecordTypeInfosByName().get('Venture Nominee').getRecordTypeId();
        myLead.Acceptable_Legal_Structure__c = 'Legal Structure 1: Candidates with pure non-profit setups';  
        myLead.Area_of_Impact__c = 'Rural';
        return myLead;
    }
}

ConvertLeadController Class

public with sharing class ConvertLeadController {
    
    public Lead mylead {get;set;}
    public String status {get;set;}
    public List<Account> accs {get;set;}
    public List<Contact> cons {get;set;}
    public ConvertLeadController() {
        cons = new List<contact>();
        accs = new List<Account>();
        mylead = new Lead();
    }
    
    //Handles lead conversion
    public PageReference ConvertLead(){
        try{
            PageReference ref;
            string leadId = ApexPages.CurrentPage().getParameters().get('id').trim();
            //status = EncodingUtil.urldecode(ApexPages.CurrentPage().getParameters().get('status'), 'UTF-8');
            status = '1st Op - Qualified for New Idea Call';
            mylead = [Select Owner.Type, Owner.IsActive, Company, FirstName,Name,Nominator__c,Reference_1__c ,Reference_2__c ,Reference_3__c ,LastName, Email, Other_Email__c, Personal_Email__c, Work_Email__c, Industry
                      From Lead Where Id=:leadId];
            
            //If Lead Owner is Queue and user has update access on the lead then set owner as the logged in user. Else return
            if(mylead.Owner.Type == 'Queue' || myLead.Owner.IsActive == false){
                if(Lead.SObjectType.getDescribe().isUpdateable()){
                    mylead.OwnerId = UserInfo.getUserId();
                    update mylead;
                }else{
                    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error,'Lead must be owned by a person, not a queue, before it can be converted.'));
                    return null;
                }
            }
            //Check if the account exists. Last modified account is selected if there are multiple accounts
            accs = [Select Id,Name,UserRecordAccess.HasEditAccess From Account Where Name=:myLead.Company And Isdeleted=false order by LastModifiedDate desc Limit 1];
            //Check if the contact exists based on lead details. The contact will be selected if
            //email on the lead matches any emails on Contact. Last modified contact is selected if there are multiple contacts  
            cons = [Select Id,Name,FirstName,LastName,email,Account.Name,Owner.Name,Country_of_Residence__c, AccountId,UserRecordAccess.HasEditAccess, Account.UserRecordAccess.HasEditAccess From Contact 
                    Where (Email =:myLead.Email OR Other_Email__c =:myLead.Email OR  Personal_Email__c =:myLead.Email OR 
                           Work_Email__c =:myLead.Email)
                    order by LastModifiedDate desc Limit 1]; 
            
            //If there is match on Contact
            if(cons != null && cons.size() >0){
                if(!cons[0].UserRecordAccess.HasEditAccess){
                    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error,
                                                               'We found a match on email with an existing contact in Salesforce, but you do not have Edit access for this Contact.  Please reach out to Salesforce Support to proceed.'));
                    return null;
                }
                if(!cons[0].Account.UserRecordAccess.HasEditAccess){
                    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error,
                                                               'We found a match with on email an existing contact in Salesforce, but you do not have Edit access for the Contact\'s Organization.  Please reach out to Salesforce Support to proceed.'));
                    return null;
                }                
                // if exact match found display the message
                if(cons[0].FirstName == myLead.FirstName && cons[0].LastName == myLead.LastName){
                    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.WARNING,
                                                               ' We found an exact match with an existing contact in Salesforce, who is part of the organization '+ cons[0].Account.Name +'.<br/>  Therefore the organization on the opportunity will be set to '+ cons[0].Account.Name +'.<br/> <ul style="list-style-type: circle;padding-top:10px;"><lh style="font-weight:bold;">Next Steps</lh><li>If '+ cons[0].Account.Name +' is not the correct organization for this candidate, you must manually update the contact and opportunity <u>after converting the lead</u>.</li><li>Click \'Convert Lead\' to proceed.</li>'));
                    return null;
                }// if partial match found display the message
                else{
                    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.WARNING,
                                                               ' We found a partial match (on email but not name) for ' + myLead.Name + ' with the contact '+ cons[0].Name +'.<br/> <ul style="list-style-type: disc;padding-top:10px;"><lh style="font-weight:bold;">Next Steps</lh><li>Review the contact details provided on this page</li><ul style="list-style-type: circle;"><li>If this is the correct contact, choose \'Convert Lead\' to proceed. <br /><i>Note that the organization on the opportunity will be set to '+ cons[0].Account.Name +'.  If this is not the correct organization for this candidate, you must manually update the contact and opportunity after converting the lead.</i> </li> <li> If this is not the correct contact, click \'Cancel and Return to Lead\'. <br /> <i>You must manually edit the email address on the lead or the existing contact so that they no longer match. This allows a new contact to be created when converting the lead.<i></li></ul></ul>'));
                    return null;
                }
            }else
            {    //If there is match on the account
                if(accs != null && accs.size() >0){   
                    if(!accs[0].UserRecordAccess.HasEditAccess){
                        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.Error,' We found a match with an existing Organization, but you do not have Edit access to the Organization.  Please reach out to Salesforce Support to proceed.'));
                        return null;
                    }
                }
                ref = convertLead2(); // proceed to convert lead
                return ref;
            }
        }catch(Exception ex){
            ApexPages.addMessages(ex);
            return null;    
        }
    } 
    // to convert lead on confirmation
    public pageReference convertLead2(){
        try{
            //Convert Lead
            Database.LeadConvert lc = new Database.LeadConvert();
            lc.setLeadId(mylead.Id);
            lc.setConvertedStatus(status);
            lc.setDoNotCreateOpportunity(false);
            //Added by Dazeworks to rename the Opportunity Name to Ashoka convention
            lc.setOpportunityName(mylead.FirstName + ' ' + mylead.LastName + ' - ' + mylead.Industry);
            
            //If there is match on Contact set the Account on opportunity to the Contacts account
            if(cons != null && cons.size() >0 ){  
                lc.setContactId(cons[0].Id);
                lc.setAccountId(cons[0].AccountId);
            }//Else if there is match on the account only set the account on opportunity and new contact
            else if(accs != null && accs.size() >0){
                lc.setAccountId(accs[0].Id);
            }
            Database.LeadConvertResult lcr = Database.convertLead(lc);
            PageReference ref;
            if(lcr.isSuccess()){ // If success redirect to newly created opportunity
                ref = new PageReference('/' + lcr.getOpportunityId());
            }
            else // If failure redirect to the lead
            {
                ref = new PageReference('/' + myLead.Id);
            }
            ref.SetRedirect(true);
            //Added by Dazeworks to create Contact role : migration from deprecated code in case 18902
            if (lcr.getContactId() != null) {
                modifyContactRole('Candidate', lcr.getContactId(), true,lcr.getOpportunityId());
            }
            if (mylead.Nominator__c != null) {
                addContactRole('Nominator', mylead.Nominator__c, false,lcr.getOpportunityId());
            } 
            if (mylead.Reference_1__c != null) {
                addContactRole('Reference', mylead.Reference_1__c , false,lcr.getOpportunityId());
            } 
            if (mylead.Reference_2__c != null) {
                addContactRole('Reference', mylead.Reference_2__c , false,lcr.getOpportunityId());
            } 
            if (mylead.Reference_3__c != null) {
                addContactRole('Reference', mylead.Reference_3__c , false,lcr.getOpportunityId());
            } 
            //ends here
            return ref;
        }catch(Exception ex){
            ApexPages.addMessages(ex);
            return null;    
        }
    }
    //Redirects back to the lead detail page.
    public PageReference Cancel(){
        string leadId = ApexPages.CurrentPage().getParameters().get('id').trim();
        PageReference ref = new PageReference('/' + leadId);
        ref.SetRedirect(true);
        return ref;
    }
    
    public void modifyContactRole(String roleName, Id contactId, Boolean isPrimary,Id opportunityId) {
        if (roleName != null) {
            OpportunityContactRole ocr = [select id, role, contactId, opportunityId, isPrimary from OpportunityContactRole where opportunityId = :opportunityId and contactId = :contactId limit 1];
            ocr.ContactId = contactId;
            ocr.Role = roleName;
            ocr.IsPrimary = isPrimary;
            update ocr;
            Engagement_Role__c engagementRole = new Engagement_Role__c();
            engagementRole.Contact__c = contactId;
            engagementRole.Opportunity__c = opportunityId;
            engagementRole.Role__c = roleName;
            engagementRole.isPrimary__c = isPrimary;
            insert engagementRole;
        }
    }
    
    public void addContactRole(String roleName, Id contactId, Boolean isPrimary,Id opportunityId) {
        if (roleName != null) {
            OpportunityContactRole ocr = new OpportunityContactRole();
            ocr.ContactId = contactId;
            ocr.OpportunityId = opportunityId;
            ocr.Role = roleName;
            ocr.IsPrimary = isPrimary;
            insert ocr;
            Engagement_Role__c engagementRole = new Engagement_Role__c();
            engagementRole.Contact__c = contactId;
            engagementRole.Opportunity__c = opportunityId;
            engagementRole.Role__c = roleName;
            engagementRole.isPrimary__c = isPrimary;
            insert engagementRole;
        }
    }    
    
}
2
  • can you run the test in PROD with debug log on? That should show you the callout source.
    – cropredy
    Jan 28, 2022 at 20:07
  • thanks somehow i didnt try that! I have a flow that send email to external recipients and it tests if its in sandbox so that it only fires in prod. no gremlins.
    – gorav
    Jan 28, 2022 at 21:26

1 Answer 1

2

One possible cause for this to happen is if you have logic in an apex class, process builder, workflow, or Flow that tests to see if it is running in sandbox or PROD prior to making a callout. In your situation, this could be where the callout is enabled in PROD but not sandbox, perhaps because there is no third party app setup to support calls from sandbox.

The downstream effects of doing DML in a testmethod will execute automation and that automation could be doing callouts.

Run the tests in PROD with debug on to discover which automation occurs prior to the callout. Then correct as necessary by adding more callout mocks to your testmethods

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.