/** * @description Test class for DocusignCompositeEnvelopeBuilder * @author Paul Huliganga * @date 2026-02-23 */ @isTest private class DocusignCompositeEnvelopeBuilderTest { /** * @description Mock HTTP callout for Docusign API */ private class DocusignMock implements HttpCalloutMock { private Integer statusCode; private String responseBody; public DocusignMock(Integer statusCode, String responseBody) { this.statusCode = statusCode; this.responseBody = responseBody; } public HTTPResponse respond(HTTPRequest req) { HttpResponse res = new HttpResponse(); res.setStatusCode(this.statusCode); res.setBody(this.responseBody); res.setStatus(this.statusCode == 201 ? 'Created' : 'Error'); return res; } } /** * @description Setup test data */ @testSetup static void setup() { // Create Custom Setting for credentials Docusign_Configuration__c config = new Docusign_Configuration__c(); config.Account_Id__c = 'test-account-id-12345'; config.Base_URL__c = 'callout:DocusignAPI'; insert config; } /** * @description Test successful envelope creation with 3 templates */ @isTest static void testSuccessfulEnvelopeCreation() { // Arrange String envelopeId = 'envelope-12345-abcde'; String mockResponse = '{"envelopeId":"' + envelopeId + '","status":"sent"}'; Test.setMock(HttpCalloutMock.class, new DocusignMock(201, mockResponse)); DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = new List{'template-1', 'template-2', 'template-3'}; req.recordId = '001000000ABC123'; req.language = 'en'; req.emailSubject = 'Test Envelope'; // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(1, results.size(), 'Should return 1 result'); System.assertEquals(true, results[0].success, 'Should be successful'); System.assertEquals(envelopeId, results[0].envelopeId, 'Should return envelope ID'); System.assertEquals(null, results[0].errorMessage, 'Should have no error message'); } /** * @description Test with single template (minimum) */ @isTest static void testSingleTemplate() { // Arrange String mockResponse = '{"envelopeId":"envelope-single","status":"sent"}'; Test.setMock(HttpCalloutMock.class, new DocusignMock(201, mockResponse)); DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = new List{'template-1'}; req.recordId = '001000000ABC123'; // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(true, results[0].success, 'Should be successful with 1 template'); } /** * @description Test with 14 templates (maximum) */ @isTest static void testMaximumTemplates() { // Arrange String mockResponse = '{"envelopeId":"envelope-max","status":"sent"}'; Test.setMock(HttpCalloutMock.class, new DocusignMock(201, mockResponse)); DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); List templateIds = new List(); for (Integer i = 1; i <= 14; i++) { templateIds.add('template-' + i); } DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = templateIds; req.recordId = '001000000ABC123'; // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(true, results[0].success, 'Should be successful with 14 templates'); } /** * @description Test with duplicate template IDs (should be deduplicated) */ @isTest static void testDuplicateTemplates() { // Arrange String mockResponse = '{"envelopeId":"envelope-dedup","status":"sent"}'; Test.setMock(HttpCalloutMock.class, new DocusignMock(201, mockResponse)); DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = new List{'template-1', 'template-2', 'template-1'}; // Duplicate req.recordId = '001000000ABC123'; // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(true, results[0].success, 'Should handle duplicates'); } /** * @description Test validation failure - no template IDs */ @isTest static void testValidationNoTemplates() { // Arrange DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = new List(); // Empty req.recordId = '001000000ABC123'; // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(false, results[0].success, 'Should fail validation'); System.assert(results[0].errorMessage.containsIgnoreCase('template'), 'Error should mention templates'); } /** * @description Test validation failure - too many templates */ @isTest static void testValidationTooManyTemplates() { // Arrange DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); List templateIds = new List(); for (Integer i = 1; i <= 15; i++) { templateIds.add('template-' + i); } DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = templateIds; // 15 templates (> max of 14) req.recordId = '001000000ABC123'; // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(false, results[0].success, 'Should fail validation'); System.assert(results[0].errorMessage.contains('Maximum 14'), 'Error should mention limit'); } /** * @description Test validation failure - no record ID */ @isTest static void testValidationNoRecordId() { // Arrange DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = new List{'template-1'}; req.recordId = ''; // Blank // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(false, results[0].success, 'Should fail validation'); System.assert(results[0].errorMessage.contains('record ID'), 'Error should mention record ID'); } /** * @description Test API error - 400 Bad Request */ @isTest static void testAPIError400() { // Arrange String mockResponse = '{"errorCode":"INVALID_REQUEST_PARAMETER","message":"Invalid template ID"}'; Test.setMock(HttpCalloutMock.class, new DocusignMock(400, mockResponse)); DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token'); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = new List{'invalid-template'}; req.recordId = '001000000ABC123'; // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(false, results[0].success, 'Should fail with API error'); System.assert(results[0].errorMessage.contains('400'), 'Error should include status code'); } /** * @description Test API error - 401 Unauthorized */ @isTest static void testAPIError401() { // Arrange String mockResponse = '{"errorCode":"USER_AUTHENTICATION_FAILED","message":"Invalid token"}'; Test.setMock(HttpCalloutMock.class, new DocusignMock(401, mockResponse)); DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'invalid-token'); DocusignEnvelopeRequest req = new DocusignEnvelopeRequest(); req.templateIds = new List{'template-1'}; req.recordId = '001000000ABC123'; // Act Test.startTest(); List results = DocusignCompositeEnvelopeBuilder.sendCompositeEnvelope(new List{req}); Test.stopTest(); // Assert System.assertEquals(false, results[0].success, 'Should fail with auth error'); System.assertNotEquals(null, results[0].errorMessage, 'Should have error message'); System.assert(String.isNotBlank(results[0].errorMessage), 'Error message should not be blank'); } }