refactor: extract request handling to global DocusignEnvelopeRequestHandler class + deployment guide
Changes: - Move validation logic to DocusignEnvelopeRequestHandler.validateRequest() - Move envelope JSON building to DocusignEnvelopeRequestHandler.buildEnvelopeJSON() - Add comprehensive test suite for handler (10 test methods) - Simplify main invocable method - focus on orchestration - Add DEPLOYMENT_AND_TESTING.md reference guide with all CLI commands - Handler is reusable for future enhancements and integrations Benefits: - Single Responsibility Principle - Reusable handler for other classes - Better testability - Clean separation of concerns
This commit is contained in:
parent
4f734f0d17
commit
2f7dcf3520
|
|
@ -0,0 +1,218 @@
|
|||
# Deployment & Testing Guide
|
||||
|
||||
Quick reference for deploying the Salesforce Composite Envelope Builder to your org and running tests.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start (Recommended)
|
||||
|
||||
### Deploy + Test in One Script
|
||||
```bash
|
||||
cd /home/paulh/.openclaw/workspace/projects/salesforce-composite-envelope-builder/composite-envelope-builder
|
||||
bash deploy-to-dev-org.sh
|
||||
```
|
||||
|
||||
This script handles:
|
||||
- ✅ Org authorization (opens browser login)
|
||||
- ✅ Code deployment
|
||||
- ✅ Unit test execution with code coverage
|
||||
- ✅ Human-readable results
|
||||
|
||||
---
|
||||
|
||||
## Manual Commands
|
||||
|
||||
### 1. Authorize Your Org (First Time Only)
|
||||
```bash
|
||||
# For Developer Edition
|
||||
sf org login web --alias dev-org --instance-url https://login.salesforce.com
|
||||
|
||||
# For Sandbox
|
||||
sf org login web --alias sandbox-org --instance-url https://test.salesforce.com
|
||||
```
|
||||
|
||||
You'll be redirected to Salesforce login. Once authorized, the org alias is saved for future deploys.
|
||||
|
||||
---
|
||||
|
||||
### 2. Deploy Code
|
||||
|
||||
**Deploy all code to your org:**
|
||||
```bash
|
||||
cd /home/paulh/.openclaw/workspace/projects/salesforce-composite-envelope-builder/composite-envelope-builder
|
||||
sf project deploy start --target-org dev-org
|
||||
```
|
||||
|
||||
**Options:**
|
||||
- `--target-org dev-org` — Use the authorized org alias (replace with your alias if different)
|
||||
- `--wait 10` — Wait up to 10 minutes for deployment to complete
|
||||
|
||||
---
|
||||
|
||||
### 3. Run Tests
|
||||
|
||||
**Run ALL unit tests with code coverage:**
|
||||
```bash
|
||||
sf apex run test --wait 10 --result-format human --code-coverage --target-org dev-org
|
||||
```
|
||||
|
||||
**Run ONLY the new handler tests:**
|
||||
```bash
|
||||
sf apex run test --class-names DocusignEnvelopeRequestHandlerTest --wait 10 --result-format human --target-org dev-org
|
||||
```
|
||||
|
||||
**Run specific test methods:**
|
||||
```bash
|
||||
sf apex run test --class-names DocusignEnvelopeRequestHandlerTest --method-names testValidateRequest_Success --wait 10 --result-format human --target-org dev-org
|
||||
```
|
||||
|
||||
**Run ALL tests with JSON output (for parsing):**
|
||||
```bash
|
||||
sf apex run test --wait 10 --result-format json --code-coverage --target-org dev-org > test-results.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Complete Deploy + Test in One Command
|
||||
|
||||
```bash
|
||||
cd /home/paulh/.openclaw/workspace/projects/salesforce-composite-envelope-builder/composite-envelope-builder && \
|
||||
sf project deploy start --target-org dev-org && \
|
||||
sf apex run test --wait 10 --result-format human --code-coverage --target-org dev-org
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Understanding Test Results
|
||||
|
||||
Sample output:
|
||||
```
|
||||
Apex Tests
|
||||
═════════════════════════════════════════════════════════════════
|
||||
|
||||
Test Results Summary [Included Code Coverage]
|
||||
═════════════════════════════════════════════════════════════════
|
||||
Outcome: Passed
|
||||
Tests Ran: 39
|
||||
Passes: 39
|
||||
Failures: 0
|
||||
Skipped: 0
|
||||
Pass Rate: 100%
|
||||
Apex Code Coverage: 92%
|
||||
```
|
||||
|
||||
**Key metrics:**
|
||||
- **Pass Rate** — Percentage of tests that passed (should be 100%)
|
||||
- **Apex Code Coverage** — Percentage of code executed by tests (should be >80%)
|
||||
- **Failures** — If > 0, review the failed test names and error messages below
|
||||
|
||||
---
|
||||
|
||||
## Post-Deployment Setup
|
||||
|
||||
After successful deployment, configure your Salesforce org:
|
||||
|
||||
### 1. Create Docusign Configuration Custom Setting
|
||||
|
||||
1. Log into your Salesforce org
|
||||
2. Go to **Setup** → **Custom Settings**
|
||||
3. Click **Manage** next to **Docusign Configuration**
|
||||
4. Click **New**
|
||||
5. Fill in:
|
||||
- **Account Id:** `{your Docusign sandbox account ID}`
|
||||
- **Base URL:** `callout:DocusignAPI`
|
||||
6. Click **Save**
|
||||
|
||||
### 2. Create Named Credential for Docusign API
|
||||
|
||||
1. Go to **Setup** → **Named Credentials**
|
||||
2. Click **New Named Credential**
|
||||
3. Fill in:
|
||||
- **Name:** `DocusignAPI`
|
||||
- **Label:** `Docusign API`
|
||||
- **URL:** `https://demo.docusign.net/restapi/v2.1`
|
||||
- **Identity Type:** Named Principal
|
||||
- **Authentication Protocol:** OAuth 2.0
|
||||
- **Authentication Provider:** DocusignOAuthProvider (if available)
|
||||
4. Click **Save**
|
||||
|
||||
### 3. Update Your Screen Flow
|
||||
|
||||
1. Go to **Flow Builder**
|
||||
2. Create or edit your Screen Flow
|
||||
3. Add an **Action** to the flow:
|
||||
- Action: **Send Composite Docusign Envelope**
|
||||
- Input Variables:
|
||||
- **Template IDs** — Comma-separated Docusign template IDs
|
||||
- **Salesforce Record ID** — {!Record.Id}
|
||||
- **Language** — en or es
|
||||
- **Email Subject** — Optional custom subject
|
||||
4. Output Variables:
|
||||
- **Envelope ID** — Unique Docusign envelope ID
|
||||
- **Success** — Boolean (true/false)
|
||||
- **Error Message** — Error details if creation failed
|
||||
5. Save and test the flow
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Authorization Issues
|
||||
```bash
|
||||
# If auth fails, try manual URL method:
|
||||
sf org login web --alias dev-org --instance-url https://login.salesforce.com --json
|
||||
|
||||
# Copy the 'url' value and paste into your browser
|
||||
```
|
||||
|
||||
### Deployment Errors
|
||||
```bash
|
||||
# Check what's in your org
|
||||
sf project list metadata --target-org dev-org
|
||||
|
||||
# Validate without deploying
|
||||
sf project validate deploy --target-org dev-org
|
||||
```
|
||||
|
||||
### Test Failures
|
||||
```bash
|
||||
# Run tests with verbose output
|
||||
sf apex run test --target-org dev-org --result-format human --code-coverage -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference: Classes Deployed
|
||||
|
||||
**Apex Classes:**
|
||||
- `DocusignCompositeEnvelopeBuilder` — Main invocable class (Flows)
|
||||
- `DocusignEnvelopeRequestHandler` — Request validation & envelope building (reusable)
|
||||
- `DocusignAPIService` — Docusign REST API wrapper
|
||||
- `DocusignCredentials` — Custom settings singleton
|
||||
|
||||
**Test Classes:**
|
||||
- `DocusignCompositeEnvelopeBuilderTest` — Tests for main invocable
|
||||
- `DocusignEnvelopeRequestHandlerTest` — Tests for request handler (10 test methods)
|
||||
- `DocusignAPIServiceTest` — Tests for API service
|
||||
- `DocusignCredentialsTest` — Tests for credentials
|
||||
|
||||
**Custom Settings:**
|
||||
- `Docusign_Configuration__c` — Stores Account ID and Base URL
|
||||
|
||||
---
|
||||
|
||||
## Next Steps After Successful Deploy
|
||||
|
||||
1. ✅ Verify all tests pass
|
||||
2. ✅ Create Docusign Configuration in Salesforce
|
||||
3. ✅ Set up Named Credential
|
||||
4. ✅ Create a test Screen Flow
|
||||
5. ✅ Test with real Docusign templates
|
||||
6. ✅ Deploy to Production (when ready)
|
||||
|
||||
---
|
||||
|
||||
For more details, see:
|
||||
- `docs/deployment-guide.md` — Detailed deployment instructions
|
||||
- `docs/api-reference.md` — API reference
|
||||
- `docs/design.md` — Architecture & design decisions
|
||||
|
|
@ -27,22 +27,11 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
|
|||
Result result = new Result();
|
||||
|
||||
try {
|
||||
// Validate inputs
|
||||
validateInputs(req);
|
||||
// Validate request using handler
|
||||
DocusignEnvelopeRequestHandler.validateRequest(req);
|
||||
|
||||
// Remove duplicates and sort alphabetically
|
||||
List<String> sortedTemplateIds = sortTemplatesAlphabetically(
|
||||
new Set<String>(req.templateIds)
|
||||
);
|
||||
|
||||
// Build composite envelope JSON
|
||||
String envelopeJSON = buildCompositeEnvelopeJSON(
|
||||
sortedTemplateIds,
|
||||
req.recordId,
|
||||
req.language,
|
||||
req.emailSubject,
|
||||
null // customFields not supported in InvocableVariable (Phase 2 enhancement)
|
||||
);
|
||||
// Build envelope JSON using handler
|
||||
String envelopeJSON = DocusignEnvelopeRequestHandler.buildEnvelopeJSON(req);
|
||||
|
||||
// Get Docusign credentials
|
||||
DocusignCredentials creds = DocusignCredentials.getInstance();
|
||||
|
|
@ -85,152 +74,6 @@ global with sharing class DocusignCompositeEnvelopeBuilder {
|
|||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Validates input parameters
|
||||
* @param req Request object to validate
|
||||
* @throws IllegalArgumentException if validation fails
|
||||
*/
|
||||
private static void validateInputs(Request req) {
|
||||
if (req.templateIds == null || req.templateIds.isEmpty()) {
|
||||
throw new IllegalArgumentException('At least one template ID is required');
|
||||
}
|
||||
|
||||
if (req.templateIds.size() > 14) {
|
||||
throw new IllegalArgumentException('Maximum 14 templates allowed per envelope');
|
||||
}
|
||||
|
||||
if (String.isBlank(req.recordId)) {
|
||||
throw new IllegalArgumentException('Salesforce record ID is required');
|
||||
}
|
||||
|
||||
// Check for null template IDs
|
||||
for (String templateId : req.templateIds) {
|
||||
if (String.isBlank(templateId)) {
|
||||
throw new IllegalArgumentException('Template ID cannot be blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Removes duplicates and sorts template IDs alphabetically
|
||||
* @param templateIdSet Set of template IDs
|
||||
* @return Sorted list of unique template IDs
|
||||
*/
|
||||
private static List<String> sortTemplatesAlphabetically(Set<String> templateIdSet) {
|
||||
List<String> sortedList = new List<String>(templateIdSet);
|
||||
sortedList.sort();
|
||||
return sortedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builds composite envelope JSON for Docusign API
|
||||
* @param templateIds List of template IDs to combine
|
||||
* @param recordId Salesforce record ID for custom fields
|
||||
* @param language Language code (en/es)
|
||||
* @param emailSubject Email subject line
|
||||
* @param customFields Map of custom field name/value pairs
|
||||
* @return JSON string for Docusign API request
|
||||
*/
|
||||
@TestVisible
|
||||
private static String buildCompositeEnvelopeJSON(
|
||||
List<String> templateIds,
|
||||
String recordId,
|
||||
String language,
|
||||
String emailSubject,
|
||||
Map<String, String> customFields
|
||||
) {
|
||||
// Build composite templates array
|
||||
List<Object> compositeTemplates = new List<Object>();
|
||||
|
||||
Integer sequence = 1;
|
||||
for (String templateId : templateIds) {
|
||||
Map<String, Object> compositeTemplate = new Map<String, Object>{
|
||||
'compositeTemplateId' => String.valueOf(sequence),
|
||||
'serverTemplates' => new List<Object>{
|
||||
new Map<String, Object>{
|
||||
'sequence' => String.valueOf(sequence),
|
||||
'templateId' => templateId
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add custom fields if this is the first template
|
||||
if (sequence == 1 && (String.isNotBlank(recordId) || customFields != null)) {
|
||||
compositeTemplate.put('inlineTemplates', buildInlineTemplates(recordId, language, customFields));
|
||||
}
|
||||
|
||||
compositeTemplates.add(compositeTemplate);
|
||||
sequence++;
|
||||
}
|
||||
|
||||
// Build envelope object
|
||||
Map<String, Object> envelope = new Map<String, Object>{
|
||||
'status' => 'sent',
|
||||
'emailSubject' => String.isNotBlank(emailSubject)
|
||||
? emailSubject
|
||||
: 'Please review and sign these forms',
|
||||
'compositeTemplates' => compositeTemplates
|
||||
};
|
||||
|
||||
return JSON.serialize(envelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builds inline templates for custom fields
|
||||
* @param recordId Salesforce record ID
|
||||
* @param language Language code
|
||||
* @param customFields Additional custom fields
|
||||
* @return List of inline template objects
|
||||
*/
|
||||
private static List<Object> buildInlineTemplates(
|
||||
String recordId,
|
||||
String language,
|
||||
Map<String, String> customFields
|
||||
) {
|
||||
List<Object> textCustomFields = new List<Object>();
|
||||
|
||||
// Add Salesforce record ID
|
||||
if (String.isNotBlank(recordId)) {
|
||||
textCustomFields.add(new Map<String, Object>{
|
||||
'name' => 'SalesforceRecordId',
|
||||
'value' => recordId,
|
||||
'show' => 'false',
|
||||
'required' => 'false'
|
||||
});
|
||||
}
|
||||
|
||||
// Add language
|
||||
if (String.isNotBlank(language)) {
|
||||
textCustomFields.add(new Map<String, Object>{
|
||||
'name' => 'Language',
|
||||
'value' => language,
|
||||
'show' => 'false',
|
||||
'required' => 'false'
|
||||
});
|
||||
}
|
||||
|
||||
// Add additional custom fields
|
||||
if (customFields != null && !customFields.isEmpty()) {
|
||||
for (String fieldName : customFields.keySet()) {
|
||||
textCustomFields.add(new Map<String, Object>{
|
||||
'name' => fieldName,
|
||||
'value' => customFields.get(fieldName),
|
||||
'show' => 'false',
|
||||
'required' => 'false'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new List<Object>{
|
||||
new Map<String, Object>{
|
||||
'sequence' => '1',
|
||||
'customFields' => new Map<String, Object>{
|
||||
'textCustomFields' => textCustomFields
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Logs API call to debug log (future: custom object)
|
||||
* @param templateCount Number of templates in envelope
|
||||
|
|
|
|||
|
|
@ -0,0 +1,173 @@
|
|||
/**
|
||||
* @description Handles request validation and envelope JSON building for Docusign composite envelopes
|
||||
* @author Paul Huliganga
|
||||
* @date 2026-02-25
|
||||
*/
|
||||
global with sharing class DocusignEnvelopeRequestHandler {
|
||||
|
||||
/**
|
||||
* @description Validates composite envelope request parameters
|
||||
* @param req Request object to validate
|
||||
* @throws IllegalArgumentException if validation fails
|
||||
*/
|
||||
public static void validateRequest(DocusignCompositeEnvelopeBuilder.Request req) {
|
||||
if (req.templateIds == null || req.templateIds.isEmpty()) {
|
||||
throw new IllegalArgumentException('At least one template ID is required');
|
||||
}
|
||||
|
||||
if (req.templateIds.size() > 14) {
|
||||
throw new IllegalArgumentException('Maximum 14 templates allowed per envelope');
|
||||
}
|
||||
|
||||
if (String.isBlank(req.recordId)) {
|
||||
throw new IllegalArgumentException('Salesforce record ID is required');
|
||||
}
|
||||
|
||||
// Check for null template IDs
|
||||
for (String templateId : req.templateIds) {
|
||||
if (String.isBlank(templateId)) {
|
||||
throw new IllegalArgumentException('Template ID cannot be blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builds composite envelope JSON for Docusign API from request
|
||||
* @param req Request object containing parameters
|
||||
* @return JSON string for Docusign API request
|
||||
*/
|
||||
public static String buildEnvelopeJSON(DocusignCompositeEnvelopeBuilder.Request req) {
|
||||
// Remove duplicates and sort alphabetically
|
||||
List<String> sortedTemplateIds = sortTemplatesAlphabetically(
|
||||
new Set<String>(req.templateIds)
|
||||
);
|
||||
|
||||
// Build composite envelope JSON
|
||||
return buildCompositeEnvelopeJSON(
|
||||
sortedTemplateIds,
|
||||
req.recordId,
|
||||
req.language,
|
||||
req.emailSubject,
|
||||
null // customFields not supported in InvocableVariable (Phase 2 enhancement)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Removes duplicates and sorts template IDs alphabetically
|
||||
* @param templateIdSet Set of template IDs
|
||||
* @return Sorted list of unique template IDs
|
||||
*/
|
||||
private static List<String> sortTemplatesAlphabetically(Set<String> templateIdSet) {
|
||||
List<String> sortedList = new List<String>(templateIdSet);
|
||||
sortedList.sort();
|
||||
return sortedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builds composite envelope JSON for Docusign API
|
||||
* @param templateIds List of template IDs to combine
|
||||
* @param recordId Salesforce record ID for custom fields
|
||||
* @param language Language code (en/es)
|
||||
* @param emailSubject Email subject line
|
||||
* @param customFields Map of custom field name/value pairs
|
||||
* @return JSON string for Docusign API request
|
||||
*/
|
||||
private static String buildCompositeEnvelopeJSON(
|
||||
List<String> templateIds,
|
||||
String recordId,
|
||||
String language,
|
||||
String emailSubject,
|
||||
Map<String, String> customFields
|
||||
) {
|
||||
// Build composite templates array
|
||||
List<Object> compositeTemplates = new List<Object>();
|
||||
|
||||
Integer sequence = 1;
|
||||
for (String templateId : templateIds) {
|
||||
Map<String, Object> compositeTemplate = new Map<String, Object>{
|
||||
'compositeTemplateId' => String.valueOf(sequence),
|
||||
'serverTemplates' => new List<Object>{
|
||||
new Map<String, Object>{
|
||||
'sequence' => String.valueOf(sequence),
|
||||
'templateId' => templateId
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Add custom fields if this is the first template
|
||||
if (sequence == 1 && (String.isNotBlank(recordId) || customFields != null)) {
|
||||
compositeTemplate.put('inlineTemplates', buildInlineTemplates(recordId, language, customFields));
|
||||
}
|
||||
|
||||
compositeTemplates.add(compositeTemplate);
|
||||
sequence++;
|
||||
}
|
||||
|
||||
// Build envelope object
|
||||
Map<String, Object> envelope = new Map<String, Object>{
|
||||
'status' => 'sent',
|
||||
'emailSubject' => String.isNotBlank(emailSubject)
|
||||
? emailSubject
|
||||
: 'Please review and sign these forms',
|
||||
'compositeTemplates' => compositeTemplates
|
||||
};
|
||||
|
||||
return JSON.serialize(envelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Builds inline templates for custom fields
|
||||
* @param recordId Salesforce record ID
|
||||
* @param language Language code
|
||||
* @param customFields Additional custom fields
|
||||
* @return List of inline template objects
|
||||
*/
|
||||
private static List<Object> buildInlineTemplates(
|
||||
String recordId,
|
||||
String language,
|
||||
Map<String, String> customFields
|
||||
) {
|
||||
List<Object> textCustomFields = new List<Object>();
|
||||
|
||||
// Add Salesforce record ID
|
||||
if (String.isNotBlank(recordId)) {
|
||||
textCustomFields.add(new Map<String, Object>{
|
||||
'name' => 'SalesforceRecordId',
|
||||
'value' => recordId,
|
||||
'show' => 'false',
|
||||
'required' => 'false'
|
||||
});
|
||||
}
|
||||
|
||||
// Add language
|
||||
if (String.isNotBlank(language)) {
|
||||
textCustomFields.add(new Map<String, Object>{
|
||||
'name' => 'Language',
|
||||
'value' => language,
|
||||
'show' => 'false',
|
||||
'required' => 'false'
|
||||
});
|
||||
}
|
||||
|
||||
// Add additional custom fields
|
||||
if (customFields != null && !customFields.isEmpty()) {
|
||||
for (String fieldName : customFields.keySet()) {
|
||||
textCustomFields.add(new Map<String, Object>{
|
||||
'name' => fieldName,
|
||||
'value' => customFields.get(fieldName),
|
||||
'show' => 'false',
|
||||
'required' => 'false'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new List<Object>{
|
||||
new Map<String, Object>{
|
||||
'sequence' => '1',
|
||||
'customFields' => new Map<String, Object>{
|
||||
'textCustomFields' => textCustomFields
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>60.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
/**
|
||||
* @description Tests for DocusignEnvelopeRequestHandler
|
||||
* @author Paul Huliganga
|
||||
* @date 2026-02-25
|
||||
*/
|
||||
@isTest
|
||||
public class DocusignEnvelopeRequestHandlerTest {
|
||||
|
||||
@isTest
|
||||
static void testValidateRequest_Success() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>{ 'template1', 'template2' };
|
||||
req.recordId = '001xx000003DHf';
|
||||
req.language = 'en';
|
||||
req.emailSubject = 'Test Subject';
|
||||
|
||||
// Test
|
||||
Test.startTest();
|
||||
DocusignEnvelopeRequestHandler.validateRequest(req);
|
||||
Test.stopTest();
|
||||
|
||||
// Verify - no exception thrown
|
||||
Assert.isTrue(true, 'Validation should pass');
|
||||
}
|
||||
|
||||
@isTest
|
||||
static void testValidateRequest_NoTemplateIds() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>();
|
||||
req.recordId = '001xx000003DHf';
|
||||
|
||||
// Test & Verify
|
||||
Test.startTest();
|
||||
try {
|
||||
DocusignEnvelopeRequestHandler.validateRequest(req);
|
||||
Assert.fail('Should throw IllegalArgumentException');
|
||||
} catch (IllegalArgumentException e) {
|
||||
Assert.isTrue(e.getMessage().contains('At least one template ID'), 'Correct error message');
|
||||
}
|
||||
Test.stopTest();
|
||||
}
|
||||
|
||||
@isTest
|
||||
static void testValidateRequest_TooManyTemplates() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>();
|
||||
for (Integer i = 0; i < 15; i++) {
|
||||
req.templateIds.add('template' + i);
|
||||
}
|
||||
req.recordId = '001xx000003DHf';
|
||||
|
||||
// Test & Verify
|
||||
Test.startTest();
|
||||
try {
|
||||
DocusignEnvelopeRequestHandler.validateRequest(req);
|
||||
Assert.fail('Should throw IllegalArgumentException');
|
||||
} catch (IllegalArgumentException e) {
|
||||
Assert.isTrue(e.getMessage().contains('Maximum 14 templates'), 'Correct error message');
|
||||
}
|
||||
Test.stopTest();
|
||||
}
|
||||
|
||||
@isTest
|
||||
static void testValidateRequest_NoRecordId() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>{ 'template1' };
|
||||
req.recordId = '';
|
||||
|
||||
// Test & Verify
|
||||
Test.startTest();
|
||||
try {
|
||||
DocusignEnvelopeRequestHandler.validateRequest(req);
|
||||
Assert.fail('Should throw IllegalArgumentException');
|
||||
} catch (IllegalArgumentException e) {
|
||||
Assert.isTrue(e.getMessage().contains('Salesforce record ID'), 'Correct error message');
|
||||
}
|
||||
Test.stopTest();
|
||||
}
|
||||
|
||||
@isTest
|
||||
static void testValidateRequest_BlankTemplateId() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>{ 'template1', '', 'template3' };
|
||||
req.recordId = '001xx000003DHf';
|
||||
|
||||
// Test & Verify
|
||||
Test.startTest();
|
||||
try {
|
||||
DocusignEnvelopeRequestHandler.validateRequest(req);
|
||||
Assert.fail('Should throw IllegalArgumentException');
|
||||
} catch (IllegalArgumentException e) {
|
||||
Assert.isTrue(e.getMessage().contains('Template ID cannot be blank'), 'Correct error message');
|
||||
}
|
||||
Test.stopTest();
|
||||
}
|
||||
|
||||
@isTest
|
||||
static void testBuildEnvelopeJSON_Success() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>{ 'template2', 'template1', 'template2' }; // duplicates and unsorted
|
||||
req.recordId = '001xx000003DHf';
|
||||
req.language = 'es';
|
||||
req.emailSubject = 'Custom Subject';
|
||||
|
||||
// Test
|
||||
Test.startTest();
|
||||
String json = DocusignEnvelopeRequestHandler.buildEnvelopeJSON(req);
|
||||
Test.stopTest();
|
||||
|
||||
// Verify
|
||||
Assert.isNotNull(json, 'JSON should be generated');
|
||||
Assert.isTrue(json.contains('sent'), 'Should contain sent status');
|
||||
Assert.isTrue(json.contains('Custom Subject'), 'Should contain custom subject');
|
||||
Assert.isTrue(json.contains('compositeTemplates'), 'Should contain compositeTemplates');
|
||||
}
|
||||
|
||||
@isTest
|
||||
static void testBuildEnvelopeJSON_DefaultSubject() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>{ 'template1' };
|
||||
req.recordId = '001xx000003DHf';
|
||||
req.emailSubject = null;
|
||||
|
||||
// Test
|
||||
Test.startTest();
|
||||
String json = DocusignEnvelopeRequestHandler.buildEnvelopeJSON(req);
|
||||
Test.stopTest();
|
||||
|
||||
// Verify
|
||||
Assert.isTrue(json.contains('Please review and sign these forms'), 'Should use default subject');
|
||||
}
|
||||
|
||||
@isTest
|
||||
static void testBuildEnvelopeJSON_TemplatesAreSorted() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>{ 'template3', 'template1', 'template2' };
|
||||
req.recordId = '001xx000003DHf';
|
||||
|
||||
// Test
|
||||
Test.startTest();
|
||||
String json = DocusignEnvelopeRequestHandler.buildEnvelopeJSON(req);
|
||||
Test.stopTest();
|
||||
|
||||
// Verify - templates should be in sorted order (template1, template2, template3)
|
||||
Integer idx1 = json.indexOf('template1');
|
||||
Integer idx2 = json.indexOf('template2');
|
||||
Integer idx3 = json.indexOf('template3');
|
||||
Assert.isTrue(idx1 < idx2 && idx2 < idx3, 'Templates should be sorted alphabetically');
|
||||
}
|
||||
|
||||
@isTest
|
||||
static void testBuildEnvelopeJSON_DuplicatesRemoved() {
|
||||
// Setup
|
||||
DocusignCompositeEnvelopeBuilder.Request req = new DocusignCompositeEnvelopeBuilder.Request();
|
||||
req.templateIds = new List<String>{ 'template1', 'template1', 'template2' };
|
||||
req.recordId = '001xx000003DHf';
|
||||
|
||||
// Test
|
||||
Test.startTest();
|
||||
String json = DocusignEnvelopeRequestHandler.buildEnvelopeJSON(req);
|
||||
Test.stopTest();
|
||||
|
||||
// Verify - should only have 2 compositeTemplate entries
|
||||
Integer count = 0;
|
||||
Integer pos = 0;
|
||||
while ((pos = json.indexOf('compositeTemplateId', pos)) != -1) {
|
||||
count++;
|
||||
pos++;
|
||||
}
|
||||
Assert.areEqual(2, count, 'Should have 2 unique templates after deduplication');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<apiVersion>60.0</apiVersion>
|
||||
<status>Active</status>
|
||||
</ApexClass>
|
||||
Loading…
Reference in New Issue