Skip to content

Commit

Permalink
Merge pull request #90 from jongpie/feature/bundled-omnistudio-support
Browse files Browse the repository at this point in the history
Bundle OmniStudio support in the logging framework
  • Loading branch information
j-fischer authored Sep 21, 2024
2 parents b1290dd + ba8cbde commit 74a5a8f
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 0 deletions.
108 changes: 108 additions & 0 deletions rflib/main/default/classes/rflib_OmniStudioRemoteActions.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* Copyright (c) 2024 Johannes Fischer <[email protected]>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name "RFLIB", the name of the copyright holder, nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
@SuppressWarnings('PMD.ClassNamingConventions')
global class rflib_OmniStudioRemoteActions implements System.Callable {
@TestVisible
private static rflib_LoggerFactory loggerFactory = rflib_LoggerUtil.getFactory();
@TestVisible
private static rflib_ApplicationEventLogger applicationEventLogger = rflib_LoggerUtil.getApplicationEventLogger();

global Object call(String action, Map<String, Object> args) {
Map<String, Object> input = (Map<String, Object>)args.get('input');
Map<String, Object> output = (Map<String, Object>)args.get('output');
Map<String, Object> options = (Map<String, Object>)args.get('options');

return this.invokeMethod(action, input, output, options);
}

private Boolean invokeMethod(String methodName, Map<String,Object> input, Map<String,Object> output, Map<String,Object> option) {
System.debug('*** Input Parameters *** ' + input);

if (methodName == 'LogMessage') {
logMessage(input);
} else if (methodName == 'LogApplicationEvent') {
logApplicationEvent(input);
} else {
output.put('errorMessage', 'Invalid method name: ' + methodName);
return false;
}

return true;
}

private static void logMessage(Map<String,Object> input) {
String context = (String)input.get('context');
String logLevel = (String)input.get('level');
String message = (String)input.get('message');
List<String> args = parseArgs((String)input.get('args'));

rflib_Logger logger = loggerFactory.createLogger(context);

switch on logLevel.toUpperCase() {
when 'FATAL' {
logger.fatal(message, args);
}
when 'ERROR' {
logger.error(message, args);
}
when 'WARN' {
logger.warn(message, args);
}
when 'INFO' {
logger.info(message, args);
}
when 'DEBUG' {
logger.debug(message, args);
}
when else {
logger.debug(message, args);
}
}
}

private static void logApplicationEvent(Map<String,Object> input) {
String eventName = (String)input.get('eventName');
String relatedRecordId = (String)input.get('relatedRecordId');
String additionalDetails = (String)input.get('additionalDetails');

applicationEventLogger.logApplicationEvent(eventName, relatedRecordId, additionalDetails);
}

private static List<String> parseArgs(String argsJson) {
if (String.isNotBlank(argsJson)) {
try {
return (List<String>) JSON.deserialize(argsJson, List<String>.class);
} catch (JSONException e) {
System.debug('Failed to parse args JSON: ' + e.getMessage());
return new List<String>();
}
}
return new List<String>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>61.0</apiVersion>
<status>Active</status>
</ApexClass>
193 changes: 193 additions & 0 deletions rflib/test/default/classes/rflib_OmniStudioRemoteActionsTest.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/*
* Copyright (c) 2024 Johannes Fischer <[email protected]>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name "RFLIB", the name of the copyright holder, nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
@IsTest
@SuppressWarnings('PMD.ClassNamingConventions')
private class rflib_OmniStudioRemoteActionsTest {

private static final rflib_MockLoggerFactory loggerFactory = new rflib_MockLoggerFactory(rflib_LogLevel.DEBUG);
private static final rflib_MockApplicationEventLogger applicationEventLogger = new rflib_MockApplicationEventLogger();

static void setup() {
rflib_OmniStudioRemoteActions.loggerFactory = loggerFactory;
rflib_OmniStudioRemoteActions.applicationEventLogger = applicationEventLogger;
}

@IsTest
private static void testLogMessage_Fatal() {
runLogMessageTest('FATAL', 'FATAL');
}

@IsTest
private static void testLogMessage_Error() {
runLogMessageTest('ERROR', 'ERROR');
}

@IsTest
private static void testLogMessage_Warn() {
runLogMessageTest('WARN', 'WARN');
}

@IsTest
private static void testLogMessage_Info() {
runLogMessageTest('INFO', 'INFO');
}

@IsTest
private static void testLogMessage_Debug() {
runLogMessageTest('DEBUG', 'DEBUG');
}

@IsTest
private static void testLogMessage_UnknownLogLevel() {
runLogMessageTest('FOOBAR', 'DEBUG');
}

@IsTest
private static void testLogApplicationEvent() {
setup();

rflib_MockApplicationEventLogger mockAppEventLogger = new rflib_MockApplicationEventLogger();
rflib_OmniStudioRemoteActions.applicationEventLogger = mockAppEventLogger;

String eventName = 'TestEvent';
String relatedRecordId = '001xx000003DHP0';
String additionalDetails = 'Additional details for the event';

Map<String, Object> input = new Map<String, Object>{
'eventName' => eventName,
'relatedRecordId' => relatedRecordId,
'additionalDetails' => additionalDetails
};
Map<String, Object> output = new Map<String, Object>();
Map<String, Object> options = new Map<String, Object>();

Map<String, Object> args = new Map<String, Object>{
'input' => input,
'output' => output,
'options' => options
};

Test.startTest();
rflib_OmniStudioRemoteActions controller = new rflib_OmniStudioRemoteActions();
Boolean result = (Boolean) controller.call('LogApplicationEvent', args);
Test.stopTest();

Assert.isTrue(result, 'Expected method to return true');
Assert.isNull(output.get('errorMessage'), 'Expected no error message');
Assert.areEqual(eventName, mockAppEventLogger.capturedEventName);
Assert.areEqual(relatedRecordId, mockAppEventLogger.capturedRelatedRecordId);
Assert.areEqual(additionalDetails, mockAppEventLogger.capturedAdditionalDetails);
}

@IsTest
private static void testInvalidMethodName() {
setup();

Map<String, Object> input = new Map<String, Object>();
Map<String, Object> output = new Map<String, Object>();
Map<String, Object> option = new Map<String, Object>();

Test.startTest();
rflib_OmniStudioRemoteActions controller = new rflib_OmniStudioRemoteActions();
Map<String, Object> callableInput = new Map<String, Object>{'input' => input, 'output' => output, 'option' => option};
Boolean result = (Boolean) controller.call('InvalidMethod', callableInput);
Test.stopTest();

Assert.isFalse(result, 'Expected method to return false');
Assert.areEqual('Invalid method name: InvalidMethod', output.get('errorMessage'));
}

@IsTest
private static void testCallMethod() {
setup();

String action = 'LogMessage';
String context = 'TestContext';
String logLevel = 'INFO';
String message = 'This is a test log message';
String argsJson = '["arg1", "arg2"]';

Map<String, Object> input = new Map<String, Object>{
'context' => context,
'level' => logLevel,
'message' => message,
'args' => argsJson
};
Map<String, Object> output = new Map<String, Object>();
Map<String, Object> options = new Map<String, Object>();

Map<String, Object> args = new Map<String, Object>{
'input' => input,
'output' => output,
'options' => options
};

Test.startTest();
rflib_OmniStudioRemoteActions controller = new rflib_OmniStudioRemoteActions();
Object result = controller.call(action, args);
Test.stopTest();

Assert.isTrue((Boolean)result, 'Expected method to return true');
Assert.isNull(output.get('errorMessage'), 'Expected no error message');
Assert.isTrue(loggerFactory.debugLogCapture.containsInAnyMessage(logLevel), 'debugLogger did not contain ' + logLevel + ' message');
}


private static void runLogMessageTest(String logLevel, String expectedLogLevel) {
setup();

String context = 'TestContext';
String message = 'This is a test log message';
String argsJson = '["arg1", "arg2"]';

Map<String, Object> input = new Map<String, Object>{
'context' => context,
'level' => logLevel,
'message' => message,
'args' => argsJson
};
Map<String, Object> output = new Map<String, Object>();
Map<String, Object> options = new Map<String, Object>();

Map<String, Object> args = new Map<String, Object>{
'input' => input,
'output' => output,
'options' => options
};

Test.startTest();
rflib_OmniStudioRemoteActions controller = new rflib_OmniStudioRemoteActions();
Boolean result = (Boolean) controller.call('LogMessage', args);
Test.stopTest();

Assert.isTrue(result, 'Expected method to return true');
Assert.isNull(output.get('errorMessage'), 'Expected no error message');
Assert.isTrue(loggerFactory.debugLogCapture.containsInAnyMessage(expectedLogLevel), 'debugLogger did not contain ' + expectedLogLevel + ' message');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>61.0</apiVersion>
<status>Active</status>
</ApexClass>

0 comments on commit 74a5a8f

Please sign in to comment.