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
penguinatorpenguinator 

Unable to save test class for trigger to production

I'm having problems saving my test class to the production environment.  In my developer account, everything works well -- the trigger fires and works as expected and my test class gets 100% code coverage of the trigger.  However, when I attempt to save this to the production organization it doesn't work because my trigger isn't active.  Because my trigger isn't active, my test class's assertions fail, and I can't test the trigger.  The chicken and the egg are butting heads.

Here's the code for the trigger:

trigger contactRatingTrigger on Contact (before insert, before update)
{
 for (Contact c : Trigger.new)
 {
  Account a = [select Id, Name, Rating from Account where Id = :c.AccountId];
  
  Map<String, Integer> accountRatingMap = new Map<String, Integer>
  {
   'Loyal Customer (more than 2 years)' => 0,
   'Customer' => 2,
   'Reserved Customer' => 0,
   'Proposal (SOW Submitted)' => 4,
   'Opportunity Identified' => 5,
   'Presentation - Made' => 6,
   'Presentation - Booked' => 7,
   'Qualified Prospect' => 8,
   'Hot Prospect' => 9,
   'Warm Prospect' => 10,
   'Prospect' => 11,
   'Suspect' => 12,
   'Territory' => 13,
   'Not Qualified - no follow up reqd' => 14,
   'Not Qualified - Out of Business' => 0,
   'Lost Customer' => 15
  };
  
  Map<Integer, String> accountRatingReverseMap = new Map<Integer, String>
  {
   2 => 'Customer',
   4 => 'Proposal (SOW Submitted)',
   5 => 'Opportunity Identified',
   6 => 'Presentation - Made',
   7 => 'Presentation - Booked',
   8 => 'Qualified Prospect',
   9 => 'Hot Prospect',
   10 => 'Warm Prospect',
   11 => 'Prospect',
   12 => 'Suspect',
   13 => 'Territory',
   14 => 'Not Qualified - no follow up reqd',
   15 => 'Lost Customer'
  };
  
  Map<String, Integer> contactRatingMap = new Map<String, Integer>
  {
   'Loyal Customer' => 1,
   'Customer' => 2,
   'Reserved Customer' => 3,
   'Proposal (SOW Submitted)' => 4,
   'Opportunity Identified' => 5,
   'Presentation - Made' => 6,
   'Presentation - Booked' => 7,
   'Qualified Prospect' => 8,
   'Hot Prospect' => 9,
   'Warm Prospect' => 10,
   'Prospect' => 11,
   'Suspect' => 12,
   'Territory' => 13,
   'Not Qualified - Not interested - no follow up reqd' => 0,
   'Not Qualified - Opted Out' => 0,
   'Not Qualified - Left company' => 0,
   'Not Qualified - Out of Business' => 0,
   'Lost Customer' => 0
  };
  
  Map<Integer, String> contactRatingReverseMap = new Map<Integer, String>
  {
   1 => 'Loyal Customer',
   2 => 'Customer',
   3 => 'Reserved Customer',
   4 => 'Proposal (SOW Submitted)',
   5 => 'Opportunity Identified',
   6 => 'Presentation - Made',
   7 => 'Presentation - Booked',
   8 => 'Qualified Prospect',
   9 => 'Hot Prospect',
   10 => 'Warm Prospect',
   11 => 'Prospect',
   12 => 'Suspect',
   13 => 'Territory'
  };
  
  Integer accountRating = accountRatingMap.get(a.Rating);
  Integer contactRating = contactRatingMap.get(c.Rating_contact__c);
  
  System.debug('accountRating = ' + accountRating + '; contactRating = ' + contactRating);
  
  // Only handle expected values and ignore all others
  //
  if (contactRating != null)
  {
   // if the account has no rating, give it the lowest priority rating
   //
   if (accountRating == null)
   {
    accountRating = 1000;
   }
   
   // Ignore any ratings which we aren't supposed to touch.  A value
   // of zero indicates that we completely skip over it
   //
   if (accountRating != 0 && contactRating != 0)
   {
    if (contactRating < accountRating)
    {
     System.debug('Contact rating of ' + c.Rating_contact__c + ' is higher priority than account rating of ' + a.Rating + '. Updating Account to match.');
     a.Rating = accountRatingReverseMap.get(contactRating);
     update a;
    }
    
    else
    {
     System.debug('Contact rating of ' + c.Rating_contact__c + ' is not higher priority than account rating of ' + a.Rating + '. Account not updated.');
    }
   }
  }
  
  else
  {
   // In this case, we'll have encountered a contact rating
   // which is not expected, send an email and don't process it
   //
   Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
   mail.setToAddresses(new String[] {'debug@example.com'});
   mail.setSubject('Salesforce APEX error: invalid rating detected!');
   mail.setPlainTextBody('Received account rating: ' + a.Rating + ' and contact rating: ' + c.Rating_contact__c);
   Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
  }
 }
}

 And for the test class:

public class testContactRatingUpdate
{
public static testMethod void test1()
{
// TEST 1:
//
// Ensure that the account with no rating, when adding a contact
// as a 'Hot Prospect' gets updated to equal 'Hot Prospect'
//
Account tA1 = new Account(Name = 'New Demo Account');
insert tA1;

Contact tC1 = new Contact(AccountId = tA1.Id,
FirstName = 'John', LastName = 'Smith',
Rating_contact__c = 'Hot Prospect');

insert tC1;

Account tRA1 = [select Id, Rating from Account where Id=:tA1.Id][0];
System.assertEquals(tRA1.Rating, 'Hot Prospect');

// TEST 2:
//
// When adding a contact with no rating, the original Test Account
// should remain as 'Hot Prospect'
//
Contact tC2 = new Contact(AccountId = tA1.Id,
FirstName = 'Larry', LastName = 'Page');

insert tC2;

Account tRA2 = [select Id, Rating from Account where Id = :tA1.Id][0];
System.assertEquals(tRA2.Rating, 'Hot Prospect');

// TEST 3:
//
// When adding a contact with an unexpected rating, the account shouldn't
// get changed
//
Account tA3 = new Account(Name = 'Test Account 3');
insert tA3;

Contact tC3 = new Contact(AccountId = tA3.Id,
FirstName = 'Bill', LastName = 'Gates',
Rating_contact__c = 'Rating does not exist');

insert tC3;

Account tRA3 = [select Id, Rating from Account where Id = :tA3.Id][0];
System.assertEquals(tRA3.Rating, null);

// TEST 4:
//
// When adding a contact with a lower rating, the account shouldn't
// get changed
//
Account tA4 = new Account(Name = 'Test Account 4', Rating = 'Hot Prospect');
insert tA4;

Contact tC4 = new Contact(AccountId = tA4.Id,
FirstName = 'Steve', LastName = 'Jobs',
Rating_contact__c = 'Warm Prospect');

insert tC4;

Account tRA4 = [select Id, Rating from Account where Id = :tA4.Id][0];
System.assertEquals(tRA4.Rating, 'Hot Prospect');

}
}

 
And here's the errors as returned by the Eclipse IDE:

Severity and Description Path Resource Location Creation Time Id
Test coverage of selected Apex Class and Trigger is 0%, at least 75% test coverage is required  Production line 1 1209178364628 8

Severity and Description Path Resource Location Creation Time Id
File only saved locally, not to server Production/src/unpackaged package.xml line 1 1209429636784 82
File only saved locally, not to server Production/src/unpackaged/classes testContactRatingUpdate.cls line 1 1209429636784 84
Save error: Associate metadata or source file failed to save.  File only saved locally, not to Salesforce. Production/src/unpackaged/classes testContactRatingUpdate.cls-meta.xml line 1 1209432400248 101

If I do this from the command line using Ant:

C:\Salesforce\Deploy>ant deployCode
Buildfile: build.xml

deployCode:

BUILD FAILED
C:\Salesforce\Deploy\build.xml:11: Failures:
Test failure, method: testContactRatingUpdate.test1 -- System.Exception: Assertion Failed: Expected: null, Actual: Hot P
rospect stack Class.testContactRatingUpdate.test1: line 20, column 9 Total time: 14 seconds

 
build.xml file:

<project name="Sample usage of Salesforce Ant tasks" default="test" basedir="." xmlns:sf="antlib:com.salesforce">

    <property file="build.properties"/>
    <property environment="env"/>

    <!-- Shows deploying code & running tests for package 'codepkg' -->
    <target name="deployCode">
      <!-- Upload the contents of the "ContactRatingPkg" package, running the tests for just 1 class -->
      <sf:deploy username="${sf.username}" password="${sf.password}"
    serverurl="${sf.serverurl}"
    deployroot="ContactRatingPkg">
      </sf:deploy>
    </target>

</project>

 build.properties file (with username and password/token stripped):

# build.properties
#

sf.username = redacted@example.com
sf.password = redactedMyTokenWasHere

# Use 'https://www.salesforce.com' for production (the default if not specified).
# Use 'https://test.salesforce.com for sandbox.
sf.serverurl = https://www.salesforce.com

# If your network requires an HTTP proxy, see http://ant.apache.org/manual/proxy.html for configuration.
#

ContactRatingPkg\package.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
    <fullName>ContactRatingPkg</fullName>
    <types>
        <members>*</members>
        <name>ApexTrigger</name>
    </types>
<types> <members>*</members> <name>ApexClass</name> </types> <version>12.0</version> </Package>

The trigger and class files have this as their [filename]-meta.xml file:

Class:
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>12.0</apiVersion>
</ApexClass>

Trigger:

<?xml version="1.0" encoding="UTF-8"?>
<ApexTrigger xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>12.0</apiVersion>
</ApexTrigger>

 

 
Any ideas on what's going wrong here?


Thanks in advance!
penguinatorpenguinator
I have a case open with Salesforce on this one.  I'll update it if I get a resolution to this situation.

In the meantime, if someone else has managed to solve this I'd appreciate some pointers on what needs to change to get this working successfully in a non-developer account.
sfdcfoxsfdcfox
Why do you insist on using Ant to deploy it? Why not just package the triggers/test code, then upload it to the AppExchange as a private, password-protected app, then install it? It seems like a lot less work that way. Although I do agree it's odd that it's not working. I read once somewhere on the forums that you have to tell the trigger to be active somewhere. Unfortunately, I can't seem to find it, nor do I know the process....
penguinatorpenguinator

sfdcfox wrote:
Why do you insist on using Ant to deploy it? Why not just package the triggers/test code, then upload it to the AppExchange as a private, password-protected app, then install it? It seems like a lot less work that way. Although I do agree it's odd that it's not working. I read once somewhere on the forums that you have to tell the trigger to be active somewhere. Unfortunately, I can't seem to find it, nor do I know the process....


I'm not insisting on any particular method -- deploying via Ant appeared to be the official way to upload Apex code.  Can you point me to documentation describing the method you suggest?
penguinatorpenguinator

sfdcfox wrote:
I read once somewhere on the forums that you have to tell the trigger to be active somewhere. Unfortunately, I can't seem to find it, nor do I know the process....

And on this point, the documentation is available from Salesforce:

https://na1.salesforce.com/help/doc/user_ed.jsp?section=integrate&loc=help&target=code_define_trigger.htm

Point 4 in the documentation says: "Select the Is Active checkbox if the trigger should be compiled and enabled. Leave this checkbox unselected if you only want to store the script in your organization's metadata."

However, the Is Active checkbox is a read-only field and cannot be set.  The instructions are valid for a developer account but do not apply from an enterprise account, which is where this particular help link and instructions were taken from.
penguinatorpenguinator
As an update, I did receive an official response to my support case after giving the Salesforce rep access to the account:

"You'll have to copy and paste to create the trigger in your production Org., if the upload does not work properly.

If you have any other questions in regards to Case XXXXXXXX, please feel free to E-mail me or call the salesforce.com Support line at (800)-667-6389, please refer to your case number.

Thank you,

Rick Lasell
Customer Support Rep."

I get the impression Salesforce doesn't even know.  I've asked for details on how exactly one does this and will post any further communications.
sfdcfoxsfdcfox
The method I've always used is to use a package from your sandbox or developer account to install it. As the documentation says, you can't modify triggers or classes in production except through the API. This is an intentional design to prevent disruptions to your production organization. I've never gotten eclipse or ant to work with Salesforce personally. Maybe I should toy with it some more, but my advice would be to use packages to install the code. If you get it working, let me know. I'd be interesting in knowing how it works.
penguinatorpenguinator

sfdcfox wrote:
The method I've always used is to use a package from your sandbox or developer account to install it. As the documentation says, you can't modify triggers or classes in production except through the API. This is an intentional design to prevent disruptions to your production organization. I've never gotten eclipse or ant to work with Salesforce personally. Maybe I should toy with it some more, but my advice would be to use packages to install the code. If you get it working, let me know. I'd be interesting in knowing how it works.


I managed to get this working from Eclipse.  The trick appears to be that you should never connect Eclipse to your production account as it just won't work.  Leave things in your developer account, choose the objects, and then select Force.com > Deploy to server... and enter in the info to the production organization.  I'm surprised that attempting to add your production account is allowed given that saving any alterations you make doesn't work.
sfdcfoxsfdcfox
Thanks for that information. I'll remember that for future reference. I'm sure I'll have a need for it some day.
alex sp 7alex sp 7
I'm working on the dynamic website that hosted on WordPress and I want to use the service of saleforce on my blog (https://starprocombat.co.uk/boxing/protective-gear/shin-guards.html)