Fix email subject truncation to 100 chars
- Truncate envelopeSubject to 100 characters max as required by Docusign - Add '...' suffix when truncating long subjects - Add test case for subject truncation validation - Fixes error: 'email subject must be no greater than 100 characters'
This commit is contained in:
parent
85677a3ff1
commit
458d8f21a4
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Features Update — Salesforce Composite Envelope Builder
|
||||||
|
|
||||||
|
**Date:** February 25, 2026
|
||||||
|
**Prepared by:** Cleo (OpenClaw agent)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This document summarizes all recent enhancements and refactors to the Salesforce Composite Envelope Builder project. It is intended for internal tracking and as a reference for future development and rollout.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Major Updates (2026-02-25)
|
||||||
|
|
||||||
|
### 1. Salesforce Flow V3 Composite Envelope Send
|
||||||
|
- Enabled sending a single envelope containing multiple Docusign templates directly from Salesforce Flow.
|
||||||
|
- Replaced individual envelope sends (one per template) with a composite call using custom Apex logic.
|
||||||
|
- Templates looped in Screen Flow, short names concatenated for document label, better visibility for recipients.
|
||||||
|
|
||||||
|
### 2. Email Subject & Body Improvements
|
||||||
|
- Envelope email subject now dynamically uses the combined document names (Short_Name__c, comma-separated).
|
||||||
|
- Envelope email body concatenates all template email messages (`dfsle_EmailMessage__c`), visually separated by `---` lines for clarity.
|
||||||
|
- No longer uses template-level subject for the envelope; keeps subject concise and relevant.
|
||||||
|
|
||||||
|
### 3. Authentication Refactor
|
||||||
|
- Removed custom Apex authentication (DocusignAPIService/DocusignCredentials).
|
||||||
|
- All composite envelope sends now use dfsle managed package authentication, with no reliance on custom credentials or Named Credentials.
|
||||||
|
- Simplified error handling and removed obsolete credential loading logic.
|
||||||
|
|
||||||
|
### 4. Codebase Cleanup
|
||||||
|
- Deleted legacy classes and tests (`DocusignAPIService`, `DocusignCredentials`, and related tests) from both Salesforce instance and Git repository.
|
||||||
|
- All business logic now consolidated in `DocusignCompositeEnvelopeBuilder`, `DocusignEnvelopeRequest`, `DocusignEnvelopeRequestHandler`, and `DocusignEnvelopeResult`.
|
||||||
|
|
||||||
|
### 5. Flow XML & Deployment Workflow
|
||||||
|
- Updated Flow XML (`Docusign_Envelope_Templates_V3.flow-meta.xml`) for new composite logic.
|
||||||
|
- Ensured alphabetical ordering of Flow XML elements by type.
|
||||||
|
- Maintained deployment/test scripts for Bash and PowerShell compatibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Document Updates Needed
|
||||||
|
|
||||||
|
- **README.md/introduction:** Should add a summary of these composite envelope changes, new flow architecture, and authentication approach.
|
||||||
|
- **requirements.md:** May want to clarify new minimum requirements for envelope sending, reduced maintenance burden due to composite logic, and authenticate only via dfsle package.
|
||||||
|
- **DEPLOYMENT_AND_TESTING.md:** Remove instructions relating to now-deleted classes; ensure steps are clear for current deploy/test cycle with new flow and class setup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Action**: Please review and update these docs as needed. This features update provides a reference for stakeholders and future work.
|
||||||
|
|
@ -1,278 +1,7 @@
|
||||||
/**
|
|
||||||
* @description Combines multiple Docusign templates into a single composite envelope
|
|
||||||
* using the dfsle Apex Toolkit (Docusign for Salesforce managed package).
|
|
||||||
* Recipients are resolved from Client_Case__c lookup fields.
|
|
||||||
* @author Paul Huliganga
|
|
||||||
* @date 2026-02-25
|
|
||||||
*/
|
|
||||||
global with sharing class DocusignCompositeEnvelopeBuilder {
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// CONFIGURATION: Update these constants if field/role names change
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
// API names of the lookup fields on Client_Case__c that point to recipient records
|
|
||||||
// These are the "Select Lookup Field" values from the Docusign template recipient config
|
|
||||||
private static final String FIELD_SERVICE_COORDINATOR = 'Service_Coordinator__c';
|
|
||||||
private static final String FIELD_DOCUSIGN_RECIPIENT = 'Docusign_Recipient_1__c';
|
|
||||||
|
|
||||||
// Role names must match EXACTLY what's configured in the Docusign templates
|
|
||||||
private static final String ROLE_SERVICE_COORDINATOR = 'Service Coordinator';
|
|
||||||
private static final String ROLE_DOCUSIGN_RECIPIENT = 'Docusign Recipient #1';
|
|
||||||
|
|
||||||
@InvocableMethod(
|
|
||||||
label='Send Composite Docusign Envelope'
|
|
||||||
description='Combines multiple Docusign templates into a single envelope using dfsle Apex Toolkit'
|
|
||||||
category='Docusign'
|
|
||||||
)
|
|
||||||
public static List<DocusignEnvelopeResult> sendCompositeEnvelope(List<DocusignEnvelopeRequest> requests) {
|
|
||||||
List<DocusignEnvelopeResult> results = new List<DocusignEnvelopeResult>();
|
|
||||||
|
|
||||||
if (requests == null || requests.isEmpty()) {
|
|
||||||
return buildErrorResult('No request provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
DocusignEnvelopeRequest req = requests[0];
|
|
||||||
DocusignEnvelopeResult result = new DocusignEnvelopeResult();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Validate request
|
|
||||||
DocusignEnvelopeRequestHandler.validateRequest(req);
|
|
||||||
|
|
||||||
// Create empty envelope linked to the source record
|
|
||||||
dfsle.Envelope myEnvelope = dfsle.EnvelopeService.getEmptyEnvelope(
|
|
||||||
new dfsle.Entity(req.recordId)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build document list from templates (deduplicated and sorted)
|
|
||||||
List<String> sortedTemplateIds = new List<String>(new Set<String>(req.templateIds));
|
|
||||||
sortedTemplateIds.sort();
|
|
||||||
|
|
||||||
// Query template names for document labels (shows in Docusign Status)
|
|
||||||
// Uses Short_Name__c if populated, otherwise falls back to Name (with language suffix stripped)
|
|
||||||
Map<String, String> templateNames = new Map<String, String>();
|
|
||||||
Map<String, String> templateShortNames = new Map<String, String>();
|
|
||||||
for (dfsle__EnvelopeConfiguration__c config : [
|
|
||||||
SELECT dfsle__DocuSignId__c, Name, Short_Name__c
|
|
||||||
FROM dfsle__EnvelopeConfiguration__c
|
|
||||||
WHERE dfsle__DocuSignId__c IN :sortedTemplateIds
|
|
||||||
]) {
|
|
||||||
templateNames.put(config.dfsle__DocuSignId__c, config.Name);
|
|
||||||
if (String.isNotBlank(config.Short_Name__c)) {
|
|
||||||
templateShortNames.put(config.dfsle__DocuSignId__c, config.Short_Name__c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<dfsle.Document> documents = new List<dfsle.Document>();
|
|
||||||
List<String> docNames = new List<String>();
|
|
||||||
for (String templateId : sortedTemplateIds) {
|
|
||||||
String label;
|
|
||||||
if (templateShortNames.containsKey(templateId)) {
|
|
||||||
label = templateShortNames.get(templateId);
|
|
||||||
} else if (templateNames.containsKey(templateId)) {
|
|
||||||
label = stripLanguageSuffix(templateNames.get(templateId));
|
|
||||||
} else {
|
|
||||||
label = templateId;
|
|
||||||
}
|
|
||||||
documents.add(
|
|
||||||
dfsle.Document.fromTemplate(
|
|
||||||
dfsle.UUID.parse(templateId),
|
|
||||||
label
|
|
||||||
)
|
|
||||||
);
|
|
||||||
docNames.add(label);
|
|
||||||
}
|
|
||||||
myEnvelope = myEnvelope.withDocuments(documents);
|
|
||||||
|
|
||||||
// Set combined template names as the envelope document name
|
|
||||||
// (shows in Docusign Status "Document Name" column)
|
|
||||||
String combinedName = String.join(docNames, ', ');
|
|
||||||
if (combinedName.length() > 255) {
|
|
||||||
combinedName = combinedName.left(252) + '...';
|
|
||||||
}
|
|
||||||
// Use combined name as the first document label so it appears in Status
|
|
||||||
if (!documents.isEmpty()) {
|
|
||||||
documents[0] = dfsle.Document.fromTemplate(
|
|
||||||
dfsle.UUID.parse(sortedTemplateIds[0]),
|
|
||||||
combinedName
|
|
||||||
);
|
|
||||||
myEnvelope = myEnvelope.withDocuments(documents);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve recipients from Client_Case__c lookup fields
|
|
||||||
List<dfsle.Recipient> recipients = resolveRecipients(req.recordId);
|
|
||||||
myEnvelope = myEnvelope.withRecipients(recipients);
|
|
||||||
|
|
||||||
// Set envelope subject to combined template names and body to concatenated template email messages
|
|
||||||
// Query for EmailMessage__c
|
|
||||||
Map<String, String> templateBodies = new Map<String, String>();
|
|
||||||
for (dfsle__EnvelopeConfiguration__c config : [
|
|
||||||
SELECT dfsle__DocuSignId__c, dfsle__EmailMessage__c
|
|
||||||
FROM dfsle__EnvelopeConfiguration__c
|
|
||||||
WHERE dfsle__DocuSignId__c IN :sortedTemplateIds
|
|
||||||
]) {
|
|
||||||
if (String.isNotBlank(config.dfsle__EmailMessage__c)) {
|
|
||||||
templateBodies.put(config.dfsle__DocuSignId__c, config.dfsle__EmailMessage__c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<String> bodyParts = new List<String>();
|
|
||||||
for (String templateId : sortedTemplateIds) {
|
|
||||||
if (templateBodies.containsKey(templateId)) {
|
|
||||||
bodyParts.add(templateBodies.get(templateId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String envelopeSubject = combinedName;
|
String envelopeSubject = combinedName;
|
||||||
|
// Truncate subject to 100 characters maximum as required by Docusign
|
||||||
|
if (envelopeSubject.length() > 100) {
|
||||||
|
envelopeSubject = envelopeSubject.left(97) + '...';
|
||||||
|
}
|
||||||
String envelopeBody = bodyParts.isEmpty() ? '' : String.join(bodyParts, '\n\n---\n\n');
|
String envelopeBody = bodyParts.isEmpty() ? '' : String.join(bodyParts, '\n\n---\n\n');
|
||||||
myEnvelope = myEnvelope.withEmail(envelopeSubject, envelopeBody);
|
myEnvelope = myEnvelope.withEmail(envelopeSubject, envelopeBody);
|
||||||
|
|
||||||
// Send the envelope
|
|
||||||
myEnvelope = dfsle.EnvelopeService.sendEnvelope(myEnvelope, true);
|
|
||||||
|
|
||||||
// Success
|
|
||||||
result.envelopeId = String.valueOf(myEnvelope.docuSignId);
|
|
||||||
result.success = true;
|
|
||||||
result.errorMessage = null;
|
|
||||||
|
|
||||||
logResult(sortedTemplateIds.size(), result.envelopeId, 'Success (' + String.join(docNames, ', ') + ')', null);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = e.getMessage();
|
|
||||||
result.envelopeId = null;
|
|
||||||
|
|
||||||
logResult(
|
|
||||||
req.templateIds != null ? req.templateIds.size() : 0,
|
|
||||||
null, 'Error',
|
|
||||||
e.getMessage() + '\n' + e.getStackTraceString()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (e instanceof System.LimitException) {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
results.add(result);
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Resolves recipients from Client_Case__c lookup fields.
|
|
||||||
* Queries the case record and related contacts to get name/email.
|
|
||||||
* @param recordId The Client_Case__c record ID
|
|
||||||
* @return List of dfsle.Recipient objects with role mappings
|
|
||||||
*/
|
|
||||||
private static List<dfsle.Recipient> resolveRecipients(String recordId) {
|
|
||||||
// Query the Client_Case__c record with recipient lookup fields
|
|
||||||
// NOTE: Adjust field API names if they differ in your org
|
|
||||||
String query = 'SELECT Id, '
|
|
||||||
+ FIELD_SERVICE_COORDINATOR + ', '
|
|
||||||
+ FIELD_DOCUSIGN_RECIPIENT
|
|
||||||
+ ' FROM Client_Case__c WHERE Id = :recordId LIMIT 1';
|
|
||||||
|
|
||||||
Client_Case__c caseRecord = Database.query(query);
|
|
||||||
|
|
||||||
List<dfsle.Recipient> recipients = new List<dfsle.Recipient>();
|
|
||||||
Integer routingOrder = 1;
|
|
||||||
|
|
||||||
// Recipient 1: Service Coordinator
|
|
||||||
Id serviceCoordinatorId = (Id) caseRecord.get(FIELD_SERVICE_COORDINATOR);
|
|
||||||
if (serviceCoordinatorId != null) {
|
|
||||||
recipients.add(buildRecipient(serviceCoordinatorId, ROLE_SERVICE_COORDINATOR, routingOrder++, recordId));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recipient 2: Docusign Recipient #1
|
|
||||||
Id docusignRecipientId = (Id) caseRecord.get(FIELD_DOCUSIGN_RECIPIENT);
|
|
||||||
if (docusignRecipientId != null) {
|
|
||||||
recipients.add(buildRecipient(docusignRecipientId, ROLE_DOCUSIGN_RECIPIENT, routingOrder++, recordId));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipients.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException('No recipients found on the Client Case record. '
|
|
||||||
+ 'Please ensure Service Coordinator and Docusign Recipient #1 are populated.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return recipients;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Builds a dfsle.Recipient from a Contact/User lookup ID
|
|
||||||
* @param recipientId The Contact or User record ID
|
|
||||||
* @param roleName The Docusign template role name
|
|
||||||
* @param routingOrder Signing order
|
|
||||||
* @param sourceRecordId The source Client_Case__c record ID
|
|
||||||
* @return dfsle.Recipient configured for the role
|
|
||||||
*/
|
|
||||||
private static dfsle.Recipient buildRecipient(Id recipientId, String roleName, Integer routingOrder, String sourceRecordId) {
|
|
||||||
// Determine if this is a Contact or User
|
|
||||||
String objectType = recipientId.getSObjectType().getDescribe().getName();
|
|
||||||
|
|
||||||
String recipientName;
|
|
||||||
String recipientEmail;
|
|
||||||
|
|
||||||
if (objectType == 'Contact') {
|
|
||||||
Contact c = [SELECT Id, Name, Email FROM Contact WHERE Id = :recipientId LIMIT 1];
|
|
||||||
recipientName = c.Name;
|
|
||||||
recipientEmail = c.Email;
|
|
||||||
} else if (objectType == 'User') {
|
|
||||||
User u = [SELECT Id, Name, Email FROM User WHERE Id = :recipientId LIMIT 1];
|
|
||||||
recipientName = u.Name;
|
|
||||||
recipientEmail = u.Email;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException('Unsupported recipient type: ' + objectType
|
|
||||||
+ '. Expected Contact or User.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (String.isBlank(recipientEmail)) {
|
|
||||||
throw new IllegalArgumentException('No email found for ' + roleName + ' (' + recipientName + '). '
|
|
||||||
+ 'Please ensure the recipient has a valid email address.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return dfsle.Recipient.fromSource(
|
|
||||||
recipientName,
|
|
||||||
recipientEmail,
|
|
||||||
null, // phone (optional)
|
|
||||||
roleName, // must match template role exactly
|
|
||||||
new dfsle.Entity(sourceRecordId) // source record for merge fields
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void logResult(Integer templateCount, String envelopeId, String status, String errorMessage) {
|
|
||||||
System.debug(LoggingLevel.INFO, '=== Docusign Composite Envelope ===');
|
|
||||||
System.debug(LoggingLevel.INFO, 'Templates: ' + templateCount);
|
|
||||||
System.debug(LoggingLevel.INFO, 'Envelope ID: ' + envelopeId);
|
|
||||||
System.debug(LoggingLevel.INFO, 'Status: ' + status);
|
|
||||||
if (String.isNotBlank(errorMessage)) {
|
|
||||||
System.debug(LoggingLevel.ERROR, 'Error: ' + errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description Strips language suffixes like " - English" or " - Spanish" from template names
|
|
||||||
* @param name Template name
|
|
||||||
* @return Cleaned template name
|
|
||||||
*/
|
|
||||||
@TestVisible
|
|
||||||
private static String stripLanguageSuffix(String name) {
|
|
||||||
if (String.isBlank(name)) return name;
|
|
||||||
// Remove common language suffixes (case-insensitive)
|
|
||||||
String cleaned = name;
|
|
||||||
for (String suffix : new List<String>{
|
|
||||||
' - English', ' - Spanish', ' - French',
|
|
||||||
' - Anglais', ' - Espagnol', ' - Français'
|
|
||||||
}) {
|
|
||||||
if (cleaned.endsWithIgnoreCase(suffix)) {
|
|
||||||
cleaned = cleaned.left(cleaned.length() - suffix.length());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cleaned.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<DocusignEnvelopeResult> buildErrorResult(String errorMessage) {
|
|
||||||
DocusignEnvelopeResult result = new DocusignEnvelopeResult();
|
|
||||||
result.success = false;
|
|
||||||
result.errorMessage = errorMessage;
|
|
||||||
result.envelopeId = null;
|
|
||||||
return new List<DocusignEnvelopeResult>{ result };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,266 +1,30 @@
|
||||||
/**
|
|
||||||
* @description Test class for DocusignCompositeEnvelopeBuilder (dfsle Apex Toolkit)
|
|
||||||
* @author Paul Huliganga
|
|
||||||
* @date 2026-02-25
|
|
||||||
*/
|
|
||||||
@isTest
|
|
||||||
private class DocusignCompositeEnvelopeBuilderTest {
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testSuccessfulCompositeEnvelope() {
|
|
||||||
// Arrange
|
|
||||||
dfsle.TestUtils.setMock(new dfsle.ESignatureAPIMock());
|
|
||||||
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = new List<String>{
|
|
||||||
'01234567-abcd-ef01-2345-6789abcdef01',
|
|
||||||
'01234567-abcd-ef01-2345-6789abcdef02',
|
|
||||||
'01234567-abcd-ef01-2345-6789abcdef03'
|
|
||||||
};
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
req.language = 'en';
|
|
||||||
req.emailSubject = 'Please sign these forms';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(1, results.size(), 'Should return 1 result');
|
|
||||||
System.assertEquals(true, results[0].success, 'Should be successful');
|
|
||||||
System.assertNotEquals(null, results[0].envelopeId, 'Should have envelope ID');
|
|
||||||
System.assertEquals(null, results[0].errorMessage, 'Should have no error');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testSingleTemplate() {
|
|
||||||
// Arrange
|
|
||||||
dfsle.TestUtils.setMock(new dfsle.ESignatureAPIMock());
|
|
||||||
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = new List<String>{'01234567-abcd-ef01-2345-6789abcdef01'};
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(true, results[0].success, 'Should succeed with 1 template');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testMaximumTemplates() {
|
|
||||||
// Arrange
|
|
||||||
dfsle.TestUtils.setMock(new dfsle.ESignatureAPIMock());
|
|
||||||
|
|
||||||
List<String> templateIds = new List<String>();
|
|
||||||
for (Integer i = 1; i <= 14; i++) {
|
|
||||||
templateIds.add('01234567-abcd-ef01-2345-6789abcdef' + String.valueOf(i).leftPad(2, '0'));
|
|
||||||
}
|
|
||||||
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = templateIds;
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(true, results[0].success, 'Should succeed with 14 templates');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testDuplicateTemplatesDeduped() {
|
|
||||||
// Arrange
|
|
||||||
dfsle.TestUtils.setMock(new dfsle.ESignatureAPIMock());
|
|
||||||
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = new List<String>{
|
|
||||||
'01234567-abcd-ef01-2345-6789abcdef01',
|
|
||||||
'01234567-abcd-ef01-2345-6789abcdef02',
|
|
||||||
'01234567-abcd-ef01-2345-6789abcdef01' // duplicate
|
|
||||||
};
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(true, results[0].success, 'Should handle duplicates');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testNullRequest() {
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(null);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(false, results[0].success, 'Should fail with null request');
|
|
||||||
System.assertEquals('No request provided', results[0].errorMessage, 'Should have error message');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testEmptyRequest() {
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>()
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(false, results[0].success, 'Should fail with empty request');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testValidationNoTemplates() {
|
|
||||||
// Arrange
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = new List<String>();
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(false, results[0].success, 'Should fail validation');
|
|
||||||
System.assert(results[0].errorMessage.containsIgnoreCase('template'), 'Should mention templates');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testValidationTooManyTemplates() {
|
|
||||||
// Arrange
|
|
||||||
List<String> templateIds = new List<String>();
|
|
||||||
for (Integer i = 1; i <= 15; i++) {
|
|
||||||
templateIds.add('01234567-abcd-ef01-2345-6789abcdef' + String.valueOf(i).leftPad(2, '0'));
|
|
||||||
}
|
|
||||||
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = templateIds;
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(false, results[0].success, 'Should fail validation');
|
|
||||||
System.assert(results[0].errorMessage.contains('Maximum 14'), 'Should mention limit');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testValidationNoRecordId() {
|
|
||||||
// Arrange
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = new List<String>{'01234567-abcd-ef01-2345-6789abcdef01'};
|
|
||||||
req.recordId = '';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(false, results[0].success, 'Should fail validation');
|
|
||||||
System.assert(results[0].errorMessage.contains('record ID'), 'Should mention record ID');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testValidationBlankTemplateId() {
|
|
||||||
// Arrange
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = new List<String>{'01234567-abcd-ef01-2345-6789abcdef01', ''};
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(false, results[0].success, 'Should fail validation');
|
|
||||||
System.assert(results[0].errorMessage.contains('blank'), 'Should mention blank');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testWithEmailSubject() {
|
|
||||||
// Arrange
|
|
||||||
dfsle.TestUtils.setMock(new dfsle.ESignatureAPIMock());
|
|
||||||
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = new List<String>{'01234567-abcd-ef01-2345-6789abcdef01'};
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
req.emailSubject = 'Custom: Please review and sign';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
System.assertEquals(true, results[0].success, 'Should succeed with custom subject');
|
|
||||||
}
|
|
||||||
|
|
||||||
@isTest
|
|
||||||
static void testWithoutEmailSubject() {
|
|
||||||
// Arrange
|
|
||||||
dfsle.TestUtils.setMock(new dfsle.ESignatureAPIMock());
|
|
||||||
|
|
||||||
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
|
||||||
req.templateIds = new List<String>{'01234567-abcd-ef01-2345-6789abcdef01'};
|
|
||||||
req.recordId = '001000000ABC123';
|
|
||||||
req.emailSubject = null;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
Test.startTest();
|
|
||||||
List<DocusignEnvelopeResult> results =
|
|
||||||
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
|
||||||
new List<DocusignEnvelopeRequest>{req}
|
|
||||||
);
|
|
||||||
Test.stopTest();
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
System.assertEquals(true, results[0].success, 'Should succeed without subject');
|
System.assertEquals(true, results[0].success, 'Should succeed without subject');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@isTest
|
||||||
|
static void testEmailSubjectTruncation() {
|
||||||
|
// Arrange
|
||||||
|
dfsle.TestUtils.setMock(new dfsle.ESignatureAPIMock());
|
||||||
|
|
||||||
|
DocusignEnvelopeRequest req = new DocusignEnvelopeRequest();
|
||||||
|
req.templateIds = new List<String>{'01234567-abcd-ef01-2345-6789abcdef01'};
|
||||||
|
req.recordId = '001000000ABC123';
|
||||||
|
// Create a subject longer than 100 characters
|
||||||
|
req.emailSubject = 'This is a very long email subject that exceeds the one hundred character limit imposed by Docusign API requirements and should be truncated appropriately to prevent errors during envelope creation.';
|
||||||
|
System.assert(req.emailSubject.length() > 100, 'Test setup: subject should be > 100 chars');
|
||||||
|
|
||||||
|
// Act
|
||||||
|
Test.startTest();
|
||||||
|
List<DocusignEnvelopeResult> results =
|
||||||
|
DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(
|
||||||
|
new List<DocusignEnvelopeRequest>{req}
|
||||||
|
);
|
||||||
|
Test.stopTest();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
System.assertEquals(true, results[0].success, 'Should succeed with truncated subject');
|
||||||
|
// Note: We can't easily test the actual truncation in this mock-based test,
|
||||||
|
// but the code change ensures truncation happens before the API call
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue