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
Felix QuinonesFelix Quinones 

Apex - Send a Calendar Invite a new Deliverable record is created.

Hello All, 

I'm trying to send a calendar invite to specific departments any time a new Deliverable record is created. To do that, I established several Apex Classes (below). After many changes, it continue giving me a Code Coverage Failure. Here, I have remove the client id, client secret key, tenant ID, and emails. 

Error: Your code coverage is 71%. You need at least 75% coverage to complete this deployment.

Questions: 

  1. How can I improve the different Apex Classes to increase coverage failure?
  2. What can I do different to received the same result? 
  3. Any free app or low cost that could do this? 

OutlookCalendarInvite

public class OutlookCalendarInvite {
    public static void sendCalendarInvite(List<npsp__Grant_Deadline__c> deadlines) {
        // Query related Opportunities
        Set<Id> oppIds = new Set<Id>();
        for (npsp__Grant_Deadline__c deadline : deadlines) {
            oppIds.add(deadline.npsp__Opportunity__c);
        }
        Map<Id, Opportunity> oppMap = new Map<Id, Opportunity>([
            SELECT Id, Name
            FROM Opportunity
            WHERE Id IN :oppIds
        ]);

        List<Messaging.SingleEmailMessage> emailMessages = new List<Messaging.SingleEmailMessage>();

		for (npsp__Grant_Deadline__c deadline : deadlines) {
            String emailAddress = getEmailByDepartment(deadline.Department__c);
            if (emailAddress == null) {
                System.debug('Email address is null for department: ' + deadline.Department__c);
                continue;
            }

            // Generate the ICS file for the deadline
            NPS_MeetingInviteGenerator icsGenerator = new NPS_MeetingInviteGenerator();
            List<Messaging.Emailfileattachment> fileAttachments = new List<Messaging.Emailfileattachment>();

			DateTime dueDate = deadline.npsp__Grant_Deadline_Due_Date__c;
			String bodyContent = 'Hello ' + deadline.Department__c + ' Team,\n\n' +
                     'Please find attached the calendar invite for grant opportunity ' + deadline.npsp__Opportunity__c + ' with the following deadline: ' + dueDate + '\n\n' +
                     'Deliverable Type: ' + deadline.npsp__Type__c + '\n\n' +
                     'Deliverable Sub-Type: ' + deadline.SubType__c + '\n\n' +
                     'Requirements: ' + deadline.npsp__Grant_Deliverable_Requirements__c + '\n\n' +
                     'Note: This is an automated message from Salesforce.';

            icsGenerator.meetingSubject = 'Invite - ' + deadline.Name;
            icsGenerator.meetingBody = bodyContent;
            icsGenerator.startDate = dueDate.format('dd/MM/yyyy hh:mm:ss a');
            icsGenerator.endDate = dueDate.addHours(1).format('dd/MM/yyyy hh:mm:ss a');
            icsGenerator.fromAddress = 'salesforce@nscaphila.org';
            icsGenerator.displayName = 'NSCA Salesforce Team';
            icsGenerator.attachmentName = 'MeetingInvite.ics';
            icsGenerator.toAddressList = new List<String> {emailAddress};

            Messaging.EmailFileAttachment meetingInviteIcs = icsGenerator.generateMeetingInvite();       
            fileAttachments.add(meetingInviteIcs);

            // Send the email with the ICS attachment
            NPS_EmailUtility emailUtils = new NPS_EmailUtility();
            emailUtils.toAddressList = new String[] {emailAddress};
            emailUtils.subject = 'Calendar Invite: ' + deadline.Name;

            OrgWideEmailAddress[] orgWideEmailAddr = [select Id from OrgWideEmailAddress];
            if (!orgWideEmailAddr.isEmpty()) {
                emailUtils.orgWideEmailAddrId = orgWideEmailAddr[0].Id;
            }

            emailUtils.body = bodyContent;
            emailUtils.emailAttachmentList = fileAttachments;

            if (!Test.isRunningTest()) {
                emailUtils.sendEmail();
            }
        }
    }

    private static String getEmailByDepartment(String department) {
        if (department == 'Finance') {
            return 'finance@emailexample.org';
        } else if (department == 'Economic Mobility') {
            return 'programs@emailexample.org';
        } else if (department == 'Human Resources') {
            return 'hr@emailexample.org';
        } else if (department == 'Executive Team') {
            return 'executive@emailexample.org';
        } else if (department == 'Property Management') {
            return 'facilities@emailexample.org';
        } else if (department == 'Resource Development') {
            return 'resources@emailexample.org';
        } else {
            return null;
        }
    }
}


OutlookCalendarInviteTest
 

@isTest
private class OutlookCalendarInviteTest {

    @isTest
    static void testSendCalendarInvite() {
        // Create test Opportunity
        Opportunity testOpportunity = new Opportunity(
            Name = 'Test Opportunity',
            CloseDate = Date.today(),
            StageName = 'Prospecting'
        );
        insert testOpportunity;

        // Create test data
        List<npsp__Grant_Deadline__c> testDeadlines = new List<npsp__Grant_Deadline__c>();
        for (Integer i = 0; i < 6; i++) {
            npsp__Grant_Deadline__c deadline = new npsp__Grant_Deadline__c();
            deadline.Name = 'Test Deadline ' + i;
            deadline.npsp__Grant_Deadline_Due_Date__c = Date.today().addDays(i);
            deadline.Department__c = getDepartmentByIndex(i);
            deadline.npsp__Opportunity__c = testOpportunity.Id;
            testDeadlines.add(deadline);
        }

        // Insert test data
        insert testDeadlines;

        // Get the list of deadline IDs
        List<Id> deadlineIds = new List<Id>();
        for (npsp__Grant_Deadline__c deadline : testDeadlines) {
            deadlineIds.add(deadline.Id);
        }

        // Call sendOutlookCalendarInvite method
        Test.startTest();
        Test.setMock(HttpCalloutMock.class, new MicrosoftGraphAPIMock());
        OutlookCalendarInvite.sendOutlookCalendarInvite(deadlineIds);
        Test.stopTest();
    }

    private static String getDepartmentByIndex(Integer index) {
        List<String> departments = new List<String>{
            'Finance',
            'Economic Mobility',
            'Human Resources',
            'Executive Team',
            'Property Management',
            'Resource Development'
        };
        return departments[Math.mod(index, departments.size())];
    }
}


OutlookCalendarEvent

public class OutlookCalendarEvent {
    public String subject;
    public Body body;
    public DateTimeTimeZone startDateTimeZone;
    public DateTimeTimeZone endDateTimeZone;
    public Boolean isAllDay;

    public class Body {
        public String contentType;
        public String content;

        public Body(String content, String contentType) {
            this.content = content;
            this.contentType = contentType;
        }
    }

    public class DateTimeTimeZone {
        public String dateTimeValue;
        public String timeZone;

        public DateTimeTimeZone(DateTime dateTimeValue, String timeZone) {
            this.dateTimeValue = dateTimeValue.format('yyyy-MM-dd\'T\'HH:mm:ss');
            this.timeZone = timeZone;
        }
    }
}
OutlookCalendarInviteMock
@isTest
global class OutlookCalendarInviteMock implements HttpCalloutMock {
    global HttpResponse respond(HttpRequest request) {
        HttpResponse response = new HttpResponse();
        response.setStatusCode(201); // Assuming successful creation of the calendar event
        response.setBody('{}'); // Set an empty JSON object as the body
        return response;
    }
}

MicrosoftGraphAPI
 
public class MicrosoftGraphAPI {
    private static final String CLIENT_ID = 'myclientid';
    private static final String CLIENT_SECRET = 'myclientsecret';
    private static final String TENANT_ID = 'mytenantid';
    public static final String MICROSOFTGRAPHENDPOINT = 'https://graph.microsoft.com';

    public class OAuth2 {
        public String token_type {get; set;}
        public String expires_in {get; set;}
        public String ext_expires_in {get; set;}
        public String access_token {get; set;}
    }

    public static OAuth2 getAccessToken() {
        String tokenEndpoint = 'https://login.microsoftonline.com/' + TENANT_ID + '/oauth2/token';

        HttpRequest req = new HttpRequest();
        req.setMethod('POST');
        req.setEndpoint(tokenEndpoint);
        req.setHeader('Content-Type', 'application/x-www-form-urlencoded');

        String body = 'grant_type=client_credentials' +
                      '&client_id=' + CLIENT_ID +
                      '&client_secret=' + CLIENT_SECRET +
                      '&resource=https://graph.microsoft.com';
        req.setBody(body);

        Http http = new Http();
        HttpResponse res = http.send(req);

        if (res.getStatusCode() == 200) {
            return (OAuth2) JSON.deserialize(res.getBody(), OAuth2.class);
        } else {
            return null;
        }
    }
}
MicrosoftGraphAPIMock
@isTest
global class MicrosoftGraphAPIMock implements HttpCalloutMock {
    global HttpResponse respond(HttpRequest req) {
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json');

        if (req.getEndpoint().contains('/oauth2/v2.0/token')) {
            res.setStatusCode(200);
            String responseBody = '{"token_type": "Bearer", "expires_in": "3599", "ext_expires_in": "3599", "access_token": "mock_access_token"}';
            res.setBody(responseBody);
        } else if (req.getEndpoint().contains('/v1.0/me/calendars/')) {
            res.setStatusCode(201);
            String responseBody = '{"id": "mock_event_id", "subject": "mock_subject", "start": {"dateTime": "2023-05-05T00:00:00.000Z", "timeZone": "UTC"}, "end": {"dateTime": "2023-05-06T00:00:00.000Z", "timeZone": "UTC"}}';
            res.setBody(responseBody);
        }
        
        return res;
    }
}
GrantDeadlineTrigger
 
trigger GrantDeadlineTrigger on npsp__Grant_Deadline__c (after insert, after update) {
    List<npsp__Grant_Deadline__c> deadlinesToNotify = new List<npsp__Grant_Deadline__c>();

    for (npsp__Grant_Deadline__c deadline : Trigger.new) {
        if (deadline.npsp__Type__c == 'Final Report' && deadline.Email_Sent__c == false) {
            deadlinesToNotify.add(deadline);
        }
    }

    if (!deadlinesToNotify.isEmpty()) {
        System.enqueueJob(new OutlookCalendarInvite(deadlinesToNotify));
    }
}