salesforce-composite-envelo.../composite-envelope-builder/force-app/main/default/classes/DocusignAPIServiceTest.cls

324 lines
12 KiB
OpenEdge ABL

/**
* @description Test class for DocusignAPIService
* @author Paul Huliganga
* @date 2026-02-23
*/
@isTest
private class DocusignAPIServiceTest {
/**
* @description Mock HTTP callout
*/
private class DocusignMock implements HttpCalloutMock {
private Integer statusCode;
private String responseBody;
private Integer callCount = 0;
public DocusignMock(Integer statusCode, String responseBody) {
this.statusCode = statusCode;
this.responseBody = responseBody;
}
public HTTPResponse respond(HTTPRequest req) {
callCount++;
HttpResponse res = new HttpResponse();
res.setStatusCode(this.statusCode);
res.setBody(this.responseBody);
res.setStatus(this.statusCode == 201 ? 'Created' : 'Error');
return res;
}
}
/**
* @description Mock that fails first time, succeeds second (for retry testing)
*/
private class RetryMock implements HttpCalloutMock {
private Integer callCount = 0;
public HTTPResponse respond(HTTPRequest req) {
callCount++;
HttpResponse res = new HttpResponse();
if (callCount == 1) {
// First call fails with 500
res.setStatusCode(500);
res.setBody('{"errorCode":"INTERNAL_ERROR","message":"Server error"}');
res.setStatus('Internal Server Error');
} else {
// Second call succeeds
res.setStatusCode(201);
res.setBody('{"envelopeId":"envelope-retry-success","status":"sent"}');
res.setStatus('Created');
}
return res;
}
}
/**
* @description Test successful envelope creation
*/
@isTest
static void testSuccessfulEnvelopeCreation() {
// Arrange
String envelopeId = 'envelope-test-12345';
String mockResponse = '{"envelopeId":"' + envelopeId + '","status":"sent"}';
Test.setMock(HttpCalloutMock.class, new DocusignMock(201, mockResponse));
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCredentials creds = DocusignCredentials.getInstance();
String envelopeJSON = '{"status":"sent","compositeTemplates":[]}';
// Act
Test.startTest();
String resultEnvelopeId = DocusignAPIService.createCompositeEnvelope(envelopeJSON, creds);
Test.stopTest();
// Assert
System.assertEquals(envelopeId, resultEnvelopeId, 'Should return envelope ID');
}
/**
* @description Test parseEnvelopeId method
*/
@isTest
static void testParseEnvelopeId() {
// Arrange
HttpResponse res = new HttpResponse();
res.setStatusCode(201);
res.setBody('{"envelopeId":"parsed-envelope-123","uri":"/envelopes/parsed-envelope-123"}');
// Act
Test.startTest();
String envelopeId = DocusignAPIService.parseEnvelopeId(res);
Test.stopTest();
// Assert
System.assertEquals('parsed-envelope-123', envelopeId, 'Should parse envelope ID correctly');
}
/**
* @description Test parseEnvelopeId with missing ID
*/
@isTest
static void testParseEnvelopeIdMissing() {
// Arrange
HttpResponse res = new HttpResponse();
res.setStatusCode(201);
res.setBody('{"status":"sent"}'); // No envelopeId field
// Act & Assert
Test.startTest();
try {
DocusignAPIService.parseEnvelopeId(res);
System.assert(false, 'Should have thrown exception');
} catch (DocusignAPIService.CalloutException e) {
System.assert(e.getMessage().contains('Envelope ID not found'), 'Should mention missing ID');
}
Test.stopTest();
}
/**
* @description Test handleAPIError for 400 Bad Request
*/
@isTest
static void testHandleAPIError400() {
// Arrange
HttpResponse res = new HttpResponse();
res.setStatusCode(400);
res.setBody('{"errorCode":"INVALID_REQUEST_PARAMETER","message":"Invalid template ID"}');
// Act & Assert
Test.startTest();
try {
DocusignAPIService.handleAPIError(res);
System.assert(false, 'Should have thrown exception');
} catch (DocusignAPIService.CalloutException e) {
System.assert(e.getMessage().contains('400'), 'Should include status code');
System.assert(e.getMessage().contains('Invalid template'), 'Should include error message');
System.assert(e.getMessage().contains('template IDs'), 'Should include guidance');
}
Test.stopTest();
}
/**
* @description Test handleAPIError for 401 Unauthorized
*/
@isTest
static void testHandleAPIError401() {
// Arrange
HttpResponse res = new HttpResponse();
res.setStatusCode(401);
res.setBody('{"errorCode":"USER_AUTHENTICATION_FAILED","message":"Invalid access token"}');
// Act & Assert
Test.startTest();
try {
DocusignAPIService.handleAPIError(res);
System.assert(false, 'Should have thrown exception');
} catch (DocusignAPIService.CalloutException e) {
System.assert(e.getMessage().contains('401'), 'Should include status code');
System.assert(e.getMessage().contains('Authentication'), 'Should mention authentication');
}
Test.stopTest();
}
/**
* @description Test handleAPIError for 403 Forbidden
*/
@isTest
static void testHandleAPIError403() {
// Arrange
HttpResponse res = new HttpResponse();
res.setStatusCode(403);
res.setBody('{"errorCode":"USER_LACKS_PERMISSIONS","message":"User cannot send envelopes"}');
// Act & Assert
Test.startTest();
try {
DocusignAPIService.handleAPIError(res);
System.assert(false, 'Should have thrown exception');
} catch (DocusignAPIService.CalloutException e) {
System.assert(e.getMessage().contains('403'), 'Should include status code');
System.assert(e.getMessage().contains('permission'), 'Should mention permission');
}
Test.stopTest();
}
/**
* @description Test handleAPIError for 404 Not Found
*/
@isTest
static void testHandleAPIError404() {
// Arrange
HttpResponse res = new HttpResponse();
res.setStatusCode(404);
res.setBody('{"errorCode":"RESOURCE_NOT_FOUND","message":"Template not found"}');
// Act & Assert
Test.startTest();
try {
DocusignAPIService.handleAPIError(res);
System.assert(false, 'Should have thrown exception');
} catch (DocusignAPIService.CalloutException e) {
System.assert(e.getMessage().contains('404'), 'Should include status code');
System.assert(e.getMessage().contains('not found'), 'Should mention template not found');
}
Test.stopTest();
}
/**
* @description Test handleAPIError for 429 Rate Limit
*/
@isTest
static void testHandleAPIError429() {
// Arrange
HttpResponse res = new HttpResponse();
res.setStatusCode(429);
res.setBody('{"errorCode":"HOURLY_APIINVOCATION_LIMIT_EXCEEDED","message":"Rate limit exceeded"}');
// Act & Assert
Test.startTest();
try {
DocusignAPIService.handleAPIError(res);
System.assert(false, 'Should have thrown exception');
} catch (DocusignAPIService.CalloutException e) {
System.assert(e.getMessage().contains('429'), 'Should include status code');
System.assert(e.getMessage().contains('rate limit'), 'Should mention rate limit');
}
Test.stopTest();
}
/**
* @description Test handleAPIError for 500 Server Error
*/
@isTest
static void testHandleAPIError500() {
// Arrange
HttpResponse res = new HttpResponse();
res.setStatusCode(500);
res.setBody('{"errorCode":"INTERNAL_ERROR","message":"Server error"}');
// Act & Assert
Test.startTest();
try {
DocusignAPIService.handleAPIError(res);
System.assert(false, 'Should have thrown exception');
} catch (DocusignAPIService.CalloutException e) {
System.assert(e.getMessage().contains('500'), 'Should include status code');
System.assert(e.getMessage().contains('server error'), 'Should mention server error');
}
Test.stopTest();
}
/**
* @description Test retry logic with transient failure
*/
@isTest
static void testRetryLogic() {
// Arrange
Test.setMock(HttpCalloutMock.class, new RetryMock());
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCredentials creds = DocusignCredentials.getInstance();
String envelopeJSON = '{"status":"sent","compositeTemplates":[]}';
// Act
Test.startTest();
String envelopeId = DocusignAPIService.createCompositeEnvelope(envelopeJSON, creds);
Test.stopTest();
// Assert
System.assertEquals('envelope-retry-success', envelopeId, 'Should succeed after retry');
}
/**
* @description Test buildCreateEnvelopeRequest
*/
@isTest
static void testBuildCreateEnvelopeRequest() {
// Arrange
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCredentials creds = DocusignCredentials.getInstance();
String envelopeJSON = '{"status":"sent"}';
// Act
Test.startTest();
HttpRequest req = DocusignAPIService.buildCreateEnvelopeRequest(envelopeJSON, creds);
Test.stopTest();
// Assert
System.assertEquals('POST', req.getMethod(), 'Should use POST method');
System.assert(req.getEndpoint().contains('/envelopes'), 'Should have envelopes endpoint');
System.assert(req.getHeader('Authorization').contains('Bearer'), 'Should have Bearer token');
System.assertEquals('application/json', req.getHeader('Content-Type'), 'Should set JSON content type');
System.assertEquals(envelopeJSON, req.getBody(), 'Should set body');
}
/**
* @description Test executeWithRetry with non-retryable error
*/
@isTest
static void testExecuteWithRetryNonRetryable() {
// Arrange
String mockResponse = '{"errorCode":"INVALID_REQUEST_PARAMETER","message":"Bad request"}';
Test.setMock(HttpCalloutMock.class, new DocusignMock(400, mockResponse));
DocusignCredentials.setTestCredentials('test-account-id', 'callout:DocusignAPI', 'test-token');
DocusignCredentials creds = DocusignCredentials.getInstance();
HttpRequest req = DocusignAPIService.buildCreateEnvelopeRequest('{}', creds);
// Act
Test.startTest();
HttpResponse res = DocusignAPIService.executeWithRetry(req, 2);
Test.stopTest();
// Assert
System.assertEquals(400, res.getStatusCode(), 'Should return 400 without retry');
}
}