• Kelly Logan (Raptek)
  • NEWBIE
  • 0 Points
  • Member since 2016
  • Sr. Consultant
  • Rapid Technologies, Inc.

  • Chatter
    Feed
  • 0
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 8
    Questions
  • 19
    Replies
Hello all,

I have created web services to be consumed by an external site. The goal is to allow them to pull information to use with our shared customers, so the Web Server Oauth flow seems like a good fit because their site's program will be able to securely hold the customer secret. The web service is testing successfully through Workbench and cURL using a standard salesforce licensed user. I would like to have the external site use a Community Plus licensed user, to match other access we are providing (visualforce pages) with these licenses.

I am having trouble determining an easy way to test a Community Plus licensed user to determine the correct URL to use for the GET request and to confirm that this is a functional method.

So, Question One - Is it possible to use a Community Plus licensed user to consume an APEX web service to access custom objects that are accessible to that user through regular login? 

Question Two - Assuming the answer to question one is "yes," Is there a cURL method to test Web Server flow so I can confirm functionality before the external developers have to start coding?

Question Three - Again, assuming this is a functional method, how do I determine the URL to use for the GET request? The Chatter API seems to be the closest doc to check and that only seems to have info on using REST API to access Community specific resources. I've tried inserting "/connect/communities/internal" but that just returns page not found.

For example, this curl command with a salesforce licensed user:
curl -v https://test.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=3MVxxxxxxxxxxxxxxxxxxxxxxxxtQde" -d "client_secret=67xxxxxxxx56" -d "username=klxxx@test.sandbox" -d "password=pxxxxxxd" -H 'X-PrettyPrint:1'
And then use the returned auth token in a get request and the web service runs, returning the desired data:
curl -X GET https://cs95.salesforce.com/services/apexrest/Scholars/v0.1/ -H "Authorization: OAuth 00Dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3QR" -H "X-PrettyPrint:1"
But if I try the same format with the Community licensed user, I get the error "invalid_grant", I'm assuming because the community system doesn't allow the user/password flow to be used. So how do I switch to Web Server, or can I with just cURL? 
curl -v https://test.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=3MVxxxxxxxxxxxxxxxxxxxxxQde" -d "client_secret=67xxxxxxxxxx56" -d "username=dxxxx@test.sandbox" -d "password=Wxxxxx1" -H 'X-PrettyPrint:1'

All pertinent advice and help appreciated - thank you!





 
We need to pull data from multiple objects that are related at different levels through master detail relationships for a sync with an external system. The top is the Contact object. In each case we want to pull data when anything in any of the related objects has been updated since the last sync (which we keep track of in a separate log object). 

Here is the list of relationships (Master -< Detail):
Contact -< Application -< Student Term -< Student Term Payment
Contact -< Application -< Appeal
Contact -< FAFSA Detail

My solution right now is to query each separately and present them in a set of lists that can be stitched together with their foreign keys. No processing needs to be done as a single object so this seems the most efficient (only pull the data that was modified, leave other related objects' data alone if it hasn't been changed) and the easiest. 

I am curious though - could this be done as a single query? I don't see how it could be, even if you started with Student Term as base because as far as I understand you can't go up one level Detail to Master then back down from the Master to a different Detail object. If there is a way to do this though I'd love to hear.

Alternately, is there a way to create something like an SQL View form that would encapsulate all these queries into a single queryable object? Like creating a multi-object report and then running it remotely through Apex and gathering the results for use?
Working on some legacy code that is using a combination of Batchable and Schedulable that seems odd. The original threw an error the last time we tried to schedule it - "Error: You must select an Apex class that has a parameterless constructor."

The original did indeed have a parameter
public static final Integer DEFAULT_BATCH_SIZE = 2000;
  global Integer batchSize;

  global ContactRollupsJob(Integer batchSize) {
    this.batchSize = batchSize;
  }
Given this seemed unnecessary, I tried creating a new version without the batch size set manually. I then noticed that there was no test code for this class, so I created that as well. The test runs successfully, but only provides 60% code coverage.  The lines not covered are the last ones in the class, the schedule and executeRollups methods. Any idea why these are not running? My next thought is that this Batchable, Schedulable mashup may not work anymore with current code and the best plan would be to split into a batch job called by a scheduled job.

Here is the new code
 
global class ContactRollupScheduled implements Database.Batchable<sObject>, Schedulable {
  public static final String CRON_EXP = '0 0 0/1 1/1 * ? *';

  global Database.QueryLocator start(Database.BatchableContext bc) {
    Id stRT = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('Student').getRecordTypeId();
    return Database.getQueryLocator([SELECT Id,cohort__r.name,cohort__c 
                                     FROM Contact
                                     WHERE RecordTypeId=:stRT]);
  }

  global void execute(Database.BatchableContext bc, List<Contact> contacts) {
    contacts = StudentTermPaymentService.setRollupPaymentInformationForContacts(contacts);
    contacts = ContactService.setRollupTermEligibilityAndDevEdInformation(contacts);
    contacts = ContactService.setRollupEducationInformation(contacts);
    update contacts;
  }

  global void finish(Database.BatchableContext bc) {

  }

  global void execute(SchedulableContext sc) {
    Database.executeBatch(this);
  }

  global static String schedule(String cron) {
    if (cron == null) {
      cron = CRON_EXP;
    }
    ContactRollupScheduled job = new ContactRollupScheduled();
    return System.schedule('Contact Rollups Job', cron, job);
  }

  global static String executeRollups() {
    ContactRollupScheduled job = new ContactRollupScheduled();
    return Database.executeBatch(job);
  }

}
And the test class
@isTest
public with sharing class ContactRollupScheduled_test {

    public static testmethod void testScheduledJob() {
        
        // Create some Contact records
        List<Contact> cons = new List<Contact>();
        for (Integer i=0; i<10; i++) {
            Contact c = new Contact(LastName = 'Tester ' + i);
            cons.add(c);
        }
        insert cons;

        Test.startTest();
        // Schedule the test job
        String jobId = System.schedule('ScheduledApexTest',
            ContactRollupScheduled.CRON_EXP, 
            new ContactRollupScheduled());   
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered,
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];
    System.assertEquals(ContactRollupScheduled.CRON_EXP,
         ct.CronExpression);

        System.debug('Scheduled Job ID: ' + jobId);
        // Stopping the test will run the job synchronously
        Test.stopTest();
        
    }
}



 
Working in NPSP, so I have a concurrent post in the Hub, but I'm thinking this may be more test class related so I'm posting here as well.

I have a simple service class that modifies a couple dates
public class VIR_ContactService {

    public static void UpdateTACertDates(List<Contact> newCons){
        for (Contact c : newCons) {
            if (c.TA_Join_Date__c != null) {
                if (c.TA_Date_Recertified__c == null || c.TA_Certification_Expiration__c == null) {
                    c.TA_Date_Recertified__c = c.TA_Join_Date__c;
                    c.TA_Certification_Expiration__c = c.TA_Join_Date__c.addYears(3);
                }
                if (c.TA_Certification_Expiration__c <= System.Date.today()) {
                    c.TA_Certification_Expiration__c = c.TA_Certification_Expiration__c.addYears(3);
                    c.TA_Date_Recertified__c = c.TA_Certification_Expiration__c.addYears(-3);
                }
            }
        }
    }

}

This is called by a TDTM trigger handler
global without sharing class VIR_CallTAUpdateCertDates_TDTM extends npsp.TDTM_Runnable {
  
  // the main entry point for TDTM to invoke our trigger handlers.
  global override npsp.TDTM_Runnable.DmlWrapper run(List<SObject> newlist, List<SObject> oldlist, 
             npsp.TDTM_Runnable.Action triggerAction, Schema.DescribeSObjectResult objResult) {
  
  	npsp.TDTM_Runnable.DmlWrapper dmlWrapper = null;

      if (triggerAction == npsp.TDTM_Runnable.Action.BeforeInsert ||
         triggerAction == npsp.TDTM_Runnable.Action.BeforeUpdate ) {
        List<Contact> newConList = (List<Contact>)newlist;
        VIR_ContactService.UpdateTACertDates(newConList);
      }
  	return dmlWrapper;
  	}
}
This works fine from the UI. I can update TA_Certification_Expiration__c on a Contact and the values will be modified as expected. The problem occurred when I created a test class.
 
@isTest
class VIR_ContactService_test {

    @isTest
    static void UpdateTACertDates_test () {
        Account a = new Account(Name='Test Vendor');
        insert a;
        
        List<Contact> conList = new List<Contact>();
        for(Integer i=0;i<3;i++){
            Contact c = new Contact(LastName='Tester'+i,Account=a);
            conList.add(c);
        }
        Date now = System.Date.today();
        conList[1].TA_Join_Date__c = now.addYears(-1);
        System.debug('Test Contacts for insert: ' + conList);
        Test.startTest();
        insert conList;

		conList = [SELECT Id,FirstName,LastName,TA_Join_Date__c,
                   TA_Date_Recertified__c,TA_Certification_Expiration__c
                   FROM Contact
                   ORDER BY LastName];
      
        conList[0].FirstName = 'Fatima';
        conList[1].FirstName = 'Frank';
        conList[1].TA_Certification_Expiration__c = now.addYears(-2);
        conList[2].FirstName = 'Fiora';
        conList[2].TA_Certification_Expiration__c = now.addYears(-1);
        System.debug('Test Contacts for update: ' + conList);
        update conList;
        
		conList = [SELECT Id,FirstName,LastName,TA_Join_Date__c,
                   TA_Date_Recertified__c,TA_Certification_Expiration__c
                   FROM Contact];

        system.assertEquals(null, conList[0].TA_Certification_Expiration__c);
        system.assertEquals(now.addYears(1), conList[1].TA_Certification_Expiration__c);
        system.assertEquals(now.addYears(2), conList[2].TA_Certification_Expiration__c);
        Test.stopTest)();
    }
}
The insert triggers and runs the service class, but the update does not. What am I missing? Is there some aspect of a test class update that is significantly different here that updates work from the UI but not in the test run?
 
Running on production and sandbox with an Enterprise instance (NPSP). Apex class operated fine with tests, smaller numbers of receivers. Able to send emails.

When it ran in production (with about 350 targets) it failed. When I tried manually running the class method from the Anonymous window I saw the error "FATAL_ERROR System.EmailException: SendEmail failed. First exception on row 0; first error: LIMIT_EXCEEDED, Too many target object ids.: []" in the logs.

The system is supposed to support 5,000 emails a day. I also am using Messaging.reserveMassEmailCapacity to confirm that enough resources are available to send the number of emails desired (no errors from that).

The code is copied below. Note that I tried adding the @future tag to increase limits and I used system.debug to confirm that only 400 Ids are being passed. I can't find any mention of TargetObjectId limits that are different than the daily mail limits. Also, according to the documentation if Messaging.reserveMassEmailCapacity is run without error, there is supposed to be no limit issues. Any idea what is going on here? This is holding up production work.
 
@future
    public static void duesMailNotice(List<Id> whoIds, List<Id> whatIds, String description, string templateId){
        Messaging.reserveMassEmailCapacity(whoIds.size());
        System.debug(System.LoggingLevel.ERROR,'Number of whoIds:' + whoIds.size());
        Messaging.MassEmailMessage mail = new Messaging.MassEmailMessage();
        mail.setTargetObjectIds(whoIds);
        mail.setWhatIds(whatIds);
        mail.setDescription(description);
        mail.setTemplateId(templateId);
        mail.setReplyTo('someone.else@ourdomain.org');
        Messaging.sendEmail(new Messaging.MassEmailMessage[] { mail });
    }

 
So for an Apex test I am using RunAs() to simulate a particular type of user for a VisualForce page controller. The controller filters access to entries by checking the current user's primary Account (school) to only show entries with the same Account.
List<User> uList = [SELECT Id, contactId, contact.accountId FROM User WHERE Id = :UserInfo.getUserId()];
Id currentSchoolId = !uList.isEmpty() ? uList[0].contact.accountId : null;

The code in the controller is working fine, but it can't pass tests because the test user obviously doesn't have a Contact and Account that match the test entries created. My strategy has been to create a new User (u)  with related Contact entry that has the Account set to match the test entries and then use RunAs(u) for the test run.
Account acc = new Account(name='Test College',recordTypeId=AccountRecordTypeInfo .get('College/University').getRecordTypeId());
insert acc;
    
Id genRT = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('General').getRecordTypeId();  
Contact con1 = new Contact(lastName='Contact', firstName = 'Test', recordTypeId=genRT, accountId=acc.Id);
insert con1;

Profile p = [SELECT Id FROM Profile WHERE Name='College - FA&CC Combined'];
User u = new User(Alias = 'standt', Email='standarduser@testorg.com',
    EmailEncodingKey='UTF-8', LastName='Tester', FirstName='FA&CC combo', LanguageLocaleKey='en_US',
    LocaleSidKey='en_US', ProfileId = p.Id, Contact = con1, ContactId = con1.Id,
    TimeZoneSidKey='America/Denver', UserName='standarduser@testorg.com');  
insert u;
u = [Select Id, LastName, ContactId From User Where Id = :u.Id];  
 
Again, this seems to work fine until the Test.startTest(). Once that runs, for some reason contact.accountId comes back null. I tried adding a debug query into the test section. Whether using the :UserInfo.getUserId() or :u.Id the resullt is the same, the User is found and returned, the related Contact Id matches, but the Account Id is null. Before the RunAs() the query returns the correct account, after it the account is blank.
    Test.startTest();
    System.RunAs(u) {

        List<User> uList = [SELECT Id, contactId, contact.accountId FROM User WHERE Id = :u.Id];
        Id currentSchoolId = !uList.isEmpty() ? uList[0].contact.accountId : null;
        System.debug('User Id:' + UserInfo.getUserId());
        System.debug('Queried User:' + uList[0]);
        System.debug('Queried User Id:' + uList[0].Id);
        System.debug('Queried Contact:' + uList[0].contactId);
        System.debug('Queried Account Id:' + uList[0].contact.accountId);
        System.debug('currentSchoolId:' + currentSchoolId);

The goal is to create a test environment where we can call the method above that checks the user's Account. Is this an issue with the RunAs() method? Is there another option? I can change the Account checking query noted above if necessary, but we need to have a way to check it that a test will allow to pass.

 
We have some visualforce pages used in Community to display information. We need to limit the listing shown by the account of the current user. In our sandbox this works fine by pulling the user's id and then their accountId from the related Contact entry.
    list<User> uList = [SELECT contact.accountId FROM User WHERE Id = :UserInfo.getUserId()];
    Id currentAccountId = uList.isEmpty() ? null : uList[0].contact.accountId;


But this doesn't seem to work in the test code. The UserInfo returns null. I know I can create a user and use System.RunAs() to run the test in context of that user, but my code to set the Contact AccountId for that test user is not working. Seems like there should be a better way of doing this.

    Account acc = new Account(name='Test College',recordTypeId=AccountRecordTypeInfo .get('College/University').getRecordTypeId());
    insert acc;
    
    Profile p = [SELECT Id FROM Profile WHERE Name='College - FA&CC Combined'];
    User u = new User(Alias = 'standt', Email='standarduser@testorg.com',
    EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
    LocaleSidKey='en_US', ProfileId = p.Id,
    TimeZoneSidKey='America/Denver', UserName='standarduser@testorg.com');  

    List<Contact> conList = [Select Id, AccountId from Contact Where Id = :u.ContactId ];
    Contact con1 = conList.isEmpty() ? null : conList[0];
    con1.AccountId = acc.Id;

So the goal is to have the current user's Contact entry list the test Account value created. Right now this is not working as the assignment (con1.AccountId = acc.Id) is coming back with "System.NullPointerException: Attempt to de-reference a null object"

Any ideas?

I have a list of SObjects Ed, each SObject SEH is a detail to a Master SObject Student. Each Master SObject Student has a lookup field to SObject Cohort that may be null.
If it the lookup field is not null I want to record Cohort.Name, indexed by that Student. Also I want the list to be unique as multiple Ed entries may point to the same Student.

This is in a trigger so I want to do one SOQL query to create a map to use inside my main for loop. What I'm thinking is that if I can create a list (say studentList) of the Master Object (Student) Ids, I can do a single map statement that will fill from [Select Id, Cohort__c.Name From Student Where Id in :studentList]. It seems like there should be a simple way to create List<Student> studentList... directly from the list of Ed.

Best I have so far is
        map<Id, Boolean> studentMap = new map<Id, Boolean>();
        List<Id> studentList = new List<Id>();
        for (Ed__c stu : edHists) {
            if (stu.Student__c != NULL) {
                if (studentMap.get(stu.Student__c) == NULL) {
                    studentMap.put(stu.Student__c, TRUE);
                    studentList.add(stu.Student__c);
                }
            }
        }
      
        map<Id, Student> studentCohortMap = new map<Id, Student>([
            Select Id, Student__r.Name
            From Student
            Where Id in :studentList AND Cohort__c != NULL
        ]);

Is there something more efficient and/or simpler that my Friday afternoon brain is just missing?
Hello all,

I have created web services to be consumed by an external site. The goal is to allow them to pull information to use with our shared customers, so the Web Server Oauth flow seems like a good fit because their site's program will be able to securely hold the customer secret. The web service is testing successfully through Workbench and cURL using a standard salesforce licensed user. I would like to have the external site use a Community Plus licensed user, to match other access we are providing (visualforce pages) with these licenses.

I am having trouble determining an easy way to test a Community Plus licensed user to determine the correct URL to use for the GET request and to confirm that this is a functional method.

So, Question One - Is it possible to use a Community Plus licensed user to consume an APEX web service to access custom objects that are accessible to that user through regular login? 

Question Two - Assuming the answer to question one is "yes," Is there a cURL method to test Web Server flow so I can confirm functionality before the external developers have to start coding?

Question Three - Again, assuming this is a functional method, how do I determine the URL to use for the GET request? The Chatter API seems to be the closest doc to check and that only seems to have info on using REST API to access Community specific resources. I've tried inserting "/connect/communities/internal" but that just returns page not found.

For example, this curl command with a salesforce licensed user:
curl -v https://test.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=3MVxxxxxxxxxxxxxxxxxxxxxxxxtQde" -d "client_secret=67xxxxxxxx56" -d "username=klxxx@test.sandbox" -d "password=pxxxxxxd" -H 'X-PrettyPrint:1'
And then use the returned auth token in a get request and the web service runs, returning the desired data:
curl -X GET https://cs95.salesforce.com/services/apexrest/Scholars/v0.1/ -H "Authorization: OAuth 00Dxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3QR" -H "X-PrettyPrint:1"
But if I try the same format with the Community licensed user, I get the error "invalid_grant", I'm assuming because the community system doesn't allow the user/password flow to be used. So how do I switch to Web Server, or can I with just cURL? 
curl -v https://test.salesforce.com/services/oauth2/token -d "grant_type=password" -d "client_id=3MVxxxxxxxxxxxxxxxxxxxxxQde" -d "client_secret=67xxxxxxxxxx56" -d "username=dxxxx@test.sandbox" -d "password=Wxxxxx1" -H 'X-PrettyPrint:1'

All pertinent advice and help appreciated - thank you!





 
We need to pull data from multiple objects that are related at different levels through master detail relationships for a sync with an external system. The top is the Contact object. In each case we want to pull data when anything in any of the related objects has been updated since the last sync (which we keep track of in a separate log object). 

Here is the list of relationships (Master -< Detail):
Contact -< Application -< Student Term -< Student Term Payment
Contact -< Application -< Appeal
Contact -< FAFSA Detail

My solution right now is to query each separately and present them in a set of lists that can be stitched together with their foreign keys. No processing needs to be done as a single object so this seems the most efficient (only pull the data that was modified, leave other related objects' data alone if it hasn't been changed) and the easiest. 

I am curious though - could this be done as a single query? I don't see how it could be, even if you started with Student Term as base because as far as I understand you can't go up one level Detail to Master then back down from the Master to a different Detail object. If there is a way to do this though I'd love to hear.

Alternately, is there a way to create something like an SQL View form that would encapsulate all these queries into a single queryable object? Like creating a multi-object report and then running it remotely through Apex and gathering the results for use?
Working on some legacy code that is using a combination of Batchable and Schedulable that seems odd. The original threw an error the last time we tried to schedule it - "Error: You must select an Apex class that has a parameterless constructor."

The original did indeed have a parameter
public static final Integer DEFAULT_BATCH_SIZE = 2000;
  global Integer batchSize;

  global ContactRollupsJob(Integer batchSize) {
    this.batchSize = batchSize;
  }
Given this seemed unnecessary, I tried creating a new version without the batch size set manually. I then noticed that there was no test code for this class, so I created that as well. The test runs successfully, but only provides 60% code coverage.  The lines not covered are the last ones in the class, the schedule and executeRollups methods. Any idea why these are not running? My next thought is that this Batchable, Schedulable mashup may not work anymore with current code and the best plan would be to split into a batch job called by a scheduled job.

Here is the new code
 
global class ContactRollupScheduled implements Database.Batchable<sObject>, Schedulable {
  public static final String CRON_EXP = '0 0 0/1 1/1 * ? *';

  global Database.QueryLocator start(Database.BatchableContext bc) {
    Id stRT = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('Student').getRecordTypeId();
    return Database.getQueryLocator([SELECT Id,cohort__r.name,cohort__c 
                                     FROM Contact
                                     WHERE RecordTypeId=:stRT]);
  }

  global void execute(Database.BatchableContext bc, List<Contact> contacts) {
    contacts = StudentTermPaymentService.setRollupPaymentInformationForContacts(contacts);
    contacts = ContactService.setRollupTermEligibilityAndDevEdInformation(contacts);
    contacts = ContactService.setRollupEducationInformation(contacts);
    update contacts;
  }

  global void finish(Database.BatchableContext bc) {

  }

  global void execute(SchedulableContext sc) {
    Database.executeBatch(this);
  }

  global static String schedule(String cron) {
    if (cron == null) {
      cron = CRON_EXP;
    }
    ContactRollupScheduled job = new ContactRollupScheduled();
    return System.schedule('Contact Rollups Job', cron, job);
  }

  global static String executeRollups() {
    ContactRollupScheduled job = new ContactRollupScheduled();
    return Database.executeBatch(job);
  }

}
And the test class
@isTest
public with sharing class ContactRollupScheduled_test {

    public static testmethod void testScheduledJob() {
        
        // Create some Contact records
        List<Contact> cons = new List<Contact>();
        for (Integer i=0; i<10; i++) {
            Contact c = new Contact(LastName = 'Tester ' + i);
            cons.add(c);
        }
        insert cons;

        Test.startTest();
        // Schedule the test job
        String jobId = System.schedule('ScheduledApexTest',
            ContactRollupScheduled.CRON_EXP, 
            new ContactRollupScheduled());   
        CronTrigger ct = [SELECT Id, CronExpression, TimesTriggered,
         NextFireTime
         FROM CronTrigger WHERE id = :jobId];
    System.assertEquals(ContactRollupScheduled.CRON_EXP,
         ct.CronExpression);

        System.debug('Scheduled Job ID: ' + jobId);
        // Stopping the test will run the job synchronously
        Test.stopTest();
        
    }
}



 
Working in NPSP, so I have a concurrent post in the Hub, but I'm thinking this may be more test class related so I'm posting here as well.

I have a simple service class that modifies a couple dates
public class VIR_ContactService {

    public static void UpdateTACertDates(List<Contact> newCons){
        for (Contact c : newCons) {
            if (c.TA_Join_Date__c != null) {
                if (c.TA_Date_Recertified__c == null || c.TA_Certification_Expiration__c == null) {
                    c.TA_Date_Recertified__c = c.TA_Join_Date__c;
                    c.TA_Certification_Expiration__c = c.TA_Join_Date__c.addYears(3);
                }
                if (c.TA_Certification_Expiration__c <= System.Date.today()) {
                    c.TA_Certification_Expiration__c = c.TA_Certification_Expiration__c.addYears(3);
                    c.TA_Date_Recertified__c = c.TA_Certification_Expiration__c.addYears(-3);
                }
            }
        }
    }

}

This is called by a TDTM trigger handler
global without sharing class VIR_CallTAUpdateCertDates_TDTM extends npsp.TDTM_Runnable {
  
  // the main entry point for TDTM to invoke our trigger handlers.
  global override npsp.TDTM_Runnable.DmlWrapper run(List<SObject> newlist, List<SObject> oldlist, 
             npsp.TDTM_Runnable.Action triggerAction, Schema.DescribeSObjectResult objResult) {
  
  	npsp.TDTM_Runnable.DmlWrapper dmlWrapper = null;

      if (triggerAction == npsp.TDTM_Runnable.Action.BeforeInsert ||
         triggerAction == npsp.TDTM_Runnable.Action.BeforeUpdate ) {
        List<Contact> newConList = (List<Contact>)newlist;
        VIR_ContactService.UpdateTACertDates(newConList);
      }
  	return dmlWrapper;
  	}
}
This works fine from the UI. I can update TA_Certification_Expiration__c on a Contact and the values will be modified as expected. The problem occurred when I created a test class.
 
@isTest
class VIR_ContactService_test {

    @isTest
    static void UpdateTACertDates_test () {
        Account a = new Account(Name='Test Vendor');
        insert a;
        
        List<Contact> conList = new List<Contact>();
        for(Integer i=0;i<3;i++){
            Contact c = new Contact(LastName='Tester'+i,Account=a);
            conList.add(c);
        }
        Date now = System.Date.today();
        conList[1].TA_Join_Date__c = now.addYears(-1);
        System.debug('Test Contacts for insert: ' + conList);
        Test.startTest();
        insert conList;

		conList = [SELECT Id,FirstName,LastName,TA_Join_Date__c,
                   TA_Date_Recertified__c,TA_Certification_Expiration__c
                   FROM Contact
                   ORDER BY LastName];
      
        conList[0].FirstName = 'Fatima';
        conList[1].FirstName = 'Frank';
        conList[1].TA_Certification_Expiration__c = now.addYears(-2);
        conList[2].FirstName = 'Fiora';
        conList[2].TA_Certification_Expiration__c = now.addYears(-1);
        System.debug('Test Contacts for update: ' + conList);
        update conList;
        
		conList = [SELECT Id,FirstName,LastName,TA_Join_Date__c,
                   TA_Date_Recertified__c,TA_Certification_Expiration__c
                   FROM Contact];

        system.assertEquals(null, conList[0].TA_Certification_Expiration__c);
        system.assertEquals(now.addYears(1), conList[1].TA_Certification_Expiration__c);
        system.assertEquals(now.addYears(2), conList[2].TA_Certification_Expiration__c);
        Test.stopTest)();
    }
}
The insert triggers and runs the service class, but the update does not. What am I missing? Is there some aspect of a test class update that is significantly different here that updates work from the UI but not in the test run?
 
Running on production and sandbox with an Enterprise instance (NPSP). Apex class operated fine with tests, smaller numbers of receivers. Able to send emails.

When it ran in production (with about 350 targets) it failed. When I tried manually running the class method from the Anonymous window I saw the error "FATAL_ERROR System.EmailException: SendEmail failed. First exception on row 0; first error: LIMIT_EXCEEDED, Too many target object ids.: []" in the logs.

The system is supposed to support 5,000 emails a day. I also am using Messaging.reserveMassEmailCapacity to confirm that enough resources are available to send the number of emails desired (no errors from that).

The code is copied below. Note that I tried adding the @future tag to increase limits and I used system.debug to confirm that only 400 Ids are being passed. I can't find any mention of TargetObjectId limits that are different than the daily mail limits. Also, according to the documentation if Messaging.reserveMassEmailCapacity is run without error, there is supposed to be no limit issues. Any idea what is going on here? This is holding up production work.
 
@future
    public static void duesMailNotice(List<Id> whoIds, List<Id> whatIds, String description, string templateId){
        Messaging.reserveMassEmailCapacity(whoIds.size());
        System.debug(System.LoggingLevel.ERROR,'Number of whoIds:' + whoIds.size());
        Messaging.MassEmailMessage mail = new Messaging.MassEmailMessage();
        mail.setTargetObjectIds(whoIds);
        mail.setWhatIds(whatIds);
        mail.setDescription(description);
        mail.setTemplateId(templateId);
        mail.setReplyTo('someone.else@ourdomain.org');
        Messaging.sendEmail(new Messaging.MassEmailMessage[] { mail });
    }

 
So for an Apex test I am using RunAs() to simulate a particular type of user for a VisualForce page controller. The controller filters access to entries by checking the current user's primary Account (school) to only show entries with the same Account.
List<User> uList = [SELECT Id, contactId, contact.accountId FROM User WHERE Id = :UserInfo.getUserId()];
Id currentSchoolId = !uList.isEmpty() ? uList[0].contact.accountId : null;

The code in the controller is working fine, but it can't pass tests because the test user obviously doesn't have a Contact and Account that match the test entries created. My strategy has been to create a new User (u)  with related Contact entry that has the Account set to match the test entries and then use RunAs(u) for the test run.
Account acc = new Account(name='Test College',recordTypeId=AccountRecordTypeInfo .get('College/University').getRecordTypeId());
insert acc;
    
Id genRT = Schema.SObjectType.Contact.getRecordTypeInfosByName().get('General').getRecordTypeId();  
Contact con1 = new Contact(lastName='Contact', firstName = 'Test', recordTypeId=genRT, accountId=acc.Id);
insert con1;

Profile p = [SELECT Id FROM Profile WHERE Name='College - FA&CC Combined'];
User u = new User(Alias = 'standt', Email='standarduser@testorg.com',
    EmailEncodingKey='UTF-8', LastName='Tester', FirstName='FA&CC combo', LanguageLocaleKey='en_US',
    LocaleSidKey='en_US', ProfileId = p.Id, Contact = con1, ContactId = con1.Id,
    TimeZoneSidKey='America/Denver', UserName='standarduser@testorg.com');  
insert u;
u = [Select Id, LastName, ContactId From User Where Id = :u.Id];  
 
Again, this seems to work fine until the Test.startTest(). Once that runs, for some reason contact.accountId comes back null. I tried adding a debug query into the test section. Whether using the :UserInfo.getUserId() or :u.Id the resullt is the same, the User is found and returned, the related Contact Id matches, but the Account Id is null. Before the RunAs() the query returns the correct account, after it the account is blank.
    Test.startTest();
    System.RunAs(u) {

        List<User> uList = [SELECT Id, contactId, contact.accountId FROM User WHERE Id = :u.Id];
        Id currentSchoolId = !uList.isEmpty() ? uList[0].contact.accountId : null;
        System.debug('User Id:' + UserInfo.getUserId());
        System.debug('Queried User:' + uList[0]);
        System.debug('Queried User Id:' + uList[0].Id);
        System.debug('Queried Contact:' + uList[0].contactId);
        System.debug('Queried Account Id:' + uList[0].contact.accountId);
        System.debug('currentSchoolId:' + currentSchoolId);

The goal is to create a test environment where we can call the method above that checks the user's Account. Is this an issue with the RunAs() method? Is there another option? I can change the Account checking query noted above if necessary, but we need to have a way to check it that a test will allow to pass.

 
We have some visualforce pages used in Community to display information. We need to limit the listing shown by the account of the current user. In our sandbox this works fine by pulling the user's id and then their accountId from the related Contact entry.
    list<User> uList = [SELECT contact.accountId FROM User WHERE Id = :UserInfo.getUserId()];
    Id currentAccountId = uList.isEmpty() ? null : uList[0].contact.accountId;


But this doesn't seem to work in the test code. The UserInfo returns null. I know I can create a user and use System.RunAs() to run the test in context of that user, but my code to set the Contact AccountId for that test user is not working. Seems like there should be a better way of doing this.

    Account acc = new Account(name='Test College',recordTypeId=AccountRecordTypeInfo .get('College/University').getRecordTypeId());
    insert acc;
    
    Profile p = [SELECT Id FROM Profile WHERE Name='College - FA&CC Combined'];
    User u = new User(Alias = 'standt', Email='standarduser@testorg.com',
    EmailEncodingKey='UTF-8', LastName='Testing', LanguageLocaleKey='en_US',
    LocaleSidKey='en_US', ProfileId = p.Id,
    TimeZoneSidKey='America/Denver', UserName='standarduser@testorg.com');  

    List<Contact> conList = [Select Id, AccountId from Contact Where Id = :u.ContactId ];
    Contact con1 = conList.isEmpty() ? null : conList[0];
    con1.AccountId = acc.Id;

So the goal is to have the current user's Contact entry list the test Account value created. Right now this is not working as the assignment (con1.AccountId = acc.Id) is coming back with "System.NullPointerException: Attempt to de-reference a null object"

Any ideas?

I have a list of SObjects Ed, each SObject SEH is a detail to a Master SObject Student. Each Master SObject Student has a lookup field to SObject Cohort that may be null.
If it the lookup field is not null I want to record Cohort.Name, indexed by that Student. Also I want the list to be unique as multiple Ed entries may point to the same Student.

This is in a trigger so I want to do one SOQL query to create a map to use inside my main for loop. What I'm thinking is that if I can create a list (say studentList) of the Master Object (Student) Ids, I can do a single map statement that will fill from [Select Id, Cohort__c.Name From Student Where Id in :studentList]. It seems like there should be a simple way to create List<Student> studentList... directly from the list of Ed.

Best I have so far is
        map<Id, Boolean> studentMap = new map<Id, Boolean>();
        List<Id> studentList = new List<Id>();
        for (Ed__c stu : edHists) {
            if (stu.Student__c != NULL) {
                if (studentMap.get(stu.Student__c) == NULL) {
                    studentMap.put(stu.Student__c, TRUE);
                    studentList.add(stu.Student__c);
                }
            }
        }
      
        map<Id, Student> studentCohortMap = new map<Id, Student>([
            Select Id, Student__r.Name
            From Student
            Where Id in :studentList AND Cohort__c != NULL
        ]);

Is there something more efficient and/or simpler that my Friday afternoon brain is just missing?
/*
 * **Created by Jack Wang 9.24.2014
*/
global class AR_Creating75DummyNML implements Database.Batchable<sObject>{
    
    static final Integer numofNML=75;
    global final String query;
    global AR_Creating75DummyNML(String q)
    {
        query=q;
    }
    
    public List<Task> createNML(List<ID> IDs)
    {
        integer size=IDs.size();
        List<integer> ints=new List<integer>();
        
        Integer numberDays = date.daysInMonth(Date.today().year(), Date.today().month());
        for(integer i=0;i<75;i++)
        {
            double k=Math.random()*size;
            ints.add(k.intValue());          
        }
        
        
        string userid=null;
        User[] users=[select id from User where name='Jeremy Young'];
        if(users.size()>0)
        {
            userid=users[0].id;
        }
        List<Task> tsks=new List<Task>();
        for(Integer i : ints)
        {
            double datek=Math.random()*numberDays;
            Task nml=new Task(ownerid=userid,CallType__c='NML',whatid=String.valueOf(IDs[i]),Subject='NML',
                              Status='Completed',Priority='Normal',ActivityDate=Date.today().tostartofMonth().addDays(datek.intValue()));
            tsks.add(nml);
        }
        return tsks;
    }
    
    global Database.QueryLocator start(Database.BatchableContext BC)
    {        
        return Database.getQueryLocator(query);        
    }
    
    global void execute(Database.BatchableContext BC, List<Account> scope)
    {
        
        Map<String,List<ID>> territoryClientIDMap=new Map<String,List<ID>>();
        for(Account client : scope)
        {
            if(territoryClientIDMap.containsKey(client.client_territories__c))
            {
                territoryClientIDMap.get(client.client_territories__c).add(client.Id);
            }
            else
            {
                territoryClientIDMap.put(client.client_territories__c, new List<ID>());
                territoryClientIDMap.get(client.client_territories__c).add(client.Id);
            }
        }
        
        for(List<ID> clientIDS : territoryClientIDMap.values())
        {
            List<Task> tsks=createNML(clientIDS);
            insert tsks;
        }
        
        
    }
    
    global void finish(Database.BatchableContext BC)
    {
        
    }

}
@isTest
private class Test_AR_Creating75DummyNML {
    
    
    static testMethod void Test(){
        //create 10 advisors
        ////for each advisor create 100 clients
        //

        string userid=null;
        User[] users=[select id from User where name='Jeremy Young'];
        if(users.size()>0)
        {
            userid=users[0].id;
        }
        List<Account> advisors=new List<Account>();
    
        For(integer i=0;i<10;i++)
        {
            Account a=new Account(name='a'+String.valueOf(i),client_territories__c='a'+String.valueOf(i),account_type__c='provider',Monthly_Activity_Reporting__c=true,Account_Status__c='active');
            advisors.add(a);
        }
        insert advisors;
        
        For(integer j=0;j<advisors.size();j++)
        {
            List<Account> clients=new List<Account>();
            For(integer i=0;i<100;i++)
            {
                Account client=new Account(name=advisors[j].name+String.valueOf(i),client_territories__c=advisors[j].client_territories__c,account_type__c='prospect');
                clients.add(client);
            }
            insert clients;
        }
        
        
        List<Account> advisors1= [select client_territories__c from Account where
                                (account_type__c='provider' or account_type__c='advisor') 
                                 and Monthly_Activity_Reporting__c=true and Account_Status__c='active'];
        List<String> territories=new List<String>();
        List<String> advisorIDs=new List<String>();
        for(Account advisor : advisors1)
        {
            territories.add(advisor.client_territories__c);
            advisorIDs.add(advisor.id);
        }        
        //[select id,client_territories__c from Account where client_territories__c in :territories];
        string tempquery='Select id,client_territories__c from Account where client_territories__c in (';        
        for(String terr : territories)
        {
            tempquery+='\'';
            tempquery+=terr;
            tempquery+='\',';
        }
        tempquery=tempquery.substring(0, tempquery.length()-1);
        tempquery+=') and id not in (';
        for(String adid : advisorIDs)
        {
			tempquery+='\'';
            tempquery+=adid;
            tempquery+='\'';
            tempquery+=',';
        }
        string query=tempquery.substring(0, tempquery.length()-1);
        query+=')';
        
        Test.startTest();
        AR_Creating75DummyNML c=new AR_Creating75DummyNML(query);
        Database.executeBatch(c);
        Test.stopTest();
        
        List<Task> tsk=[select ownerid,CallType__c,Subject,
                              Status,Priority,ActivityDate from Task where createddate=TODAY and CallType__c='NML'
                       and ownerid= :userid and Status='Completed' and Priority='Normal'];
        
        //75 tasks for each advisor
        System.assertEquals(750, tsk.size());
        
        
	}

}

I tried to google this error, but I didn't get a satisfied answer.


  • September 24, 2014
  • Like
  • 0