324 lines
12 KiB
OpenEdge ABL
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');
|
|
}
|
|
}
|