diff --git a/dlrs/main/classes/RollupController.cls b/dlrs/main/classes/RollupController.cls index 6a1c9d83..2eebad87 100644 --- a/dlrs/main/classes/RollupController.cls +++ b/dlrs/main/classes/RollupController.cls @@ -28,8 +28,8 @@ * Handles the Manage Trigger and Calculate Custom Buttons **/ public with sharing class RollupController { - - public static String API_VERSION = '58.0'; + @TestVisible + private static String FALLBACK_COMPONENT_API_VERSION = '58.0'; public String ZipData { get; set; } @@ -63,6 +63,9 @@ public with sharing class RollupController { public Boolean MetadataConnectionError { get; set; } + @TestVisible + private String componentApiVersion; + public RollupController(ApexPages.StandardController standardController) { // Query Lookup Rollup Summary record this( @@ -101,7 +104,7 @@ public with sharing class RollupController { return '' + '' + '' + - API_VERSION + + componentApiVersion + '' + ''; else @@ -135,7 +138,7 @@ public with sharing class RollupController { '') : '') + '' + - API_VERSION + + componentApiVersion + '' + ''; } @@ -170,7 +173,7 @@ public with sharing class RollupController { '') : '') + '' + - API_VERSION + + componentApiVersion + '' + ''; } @@ -183,7 +186,7 @@ public with sharing class RollupController { return '' + '' + '' + - API_VERSION + + componentApiVersion + '' + 'Active' + ''; @@ -228,7 +231,7 @@ public with sharing class RollupController { return '' + '' + '' + - API_VERSION + + componentApiVersion + '' + 'Active' + ''; @@ -269,7 +272,7 @@ public with sharing class RollupController { return '' + '' + '' + - API_VERSION + + componentApiVersion + '' + 'Active' + ''; @@ -316,7 +319,7 @@ public with sharing class RollupController { return '' + '' + '' + - API_VERSION + + componentApiVersion + '' + 'Active' + ''; @@ -403,12 +406,12 @@ public with sharing class RollupController { new ApexPages.Message( ApexPages.Severity.Error, deployMessage.fileName + - ' (Line: ' + - deployMessage.lineNumber + - ': Column:' + - deployMessage.columnNumber + - ') : ' + - deployMessage.problem + ' (Line: ' + + deployMessage.lineNumber + + ': Column:' + + deployMessage.columnNumber + + ') : ' + + deployMessage.problem ) ); // Test errors? @@ -421,12 +424,12 @@ public with sharing class RollupController { new ApexPages.Message( ApexPages.Severity.Error, testFailure.name + - '.' + - testFailure.methodName + - ' ' + - testFailure.message + - ' ' + - testFailure.stackTrace + '.' + + testFailure.methodName + + ' ' + + testFailure.message + + ' ' + + testFailure.stackTrace ) ); // Code coverage warnings? @@ -438,11 +441,11 @@ public with sharing class RollupController { new ApexPages.Message( ApexPages.Severity.Warning, (codeCoverageWarning.namespace != null - ? codeCoverageWarning.namespace + '.' - : '') + - codeCoverageWarning.name + - ':' + - codeCoverageWarning.message + ? codeCoverageWarning.namespace + '.' + : '') + + codeCoverageWarning.name + + ':' + + codeCoverageWarning.message ) ); @@ -493,6 +496,7 @@ public with sharing class RollupController { ); return; } + componentApiVersion = getNewestApiVersion(); // Already deployed? Set triggerNames = new Set{ RollupTriggerName }; @@ -566,8 +570,8 @@ public with sharing class RollupController { new ApexPages.Message( ApexPages.Severity.Info, 'Apex Trigger ' + - RollupParentTriggerTestName + - ' is installed.' + RollupParentTriggerTestName + + ' is installed.' ) ); } @@ -587,6 +591,39 @@ public with sharing class RollupController { } } + /** + * Call salesforce API to get current max supported API version + */ + private String getNewestApiVersion() { + HttpRequest req = new HttpRequest(); + req.setMethod('GET'); + // submit a Versions request + req.setEndpoint(URL.getOrgDomainUrl().toExternalForm() + '/services/data/'); + // Salesforce REST API Dev Guide says this endpoint doesn't need authentication + + try { + Http http = new Http(); + HTTPResponse res = http.send(req); + if (res.getStatusCode() != 200) { + return FALLBACK_COMPONENT_API_VERSION; + } + String body = res.getBody(); + List data = (List) JSON.deserializeUntyped(body); + Decimal maxVersion = Decimal.valueOf(FALLBACK_COMPONENT_API_VERSION); + for (Object obj : data) { + Map apiV = (Map) obj; + Decimal newV = Decimal.valueOf((String) apiV.get('version')); + if (newV > maxVersion) { + maxVersion = newV; + } + } + return String.valueOf(maxVersion); + } catch (Exception e) { + System.debug(e); + return FALLBACK_COMPONENT_API_VERSION; + } + } + private static MetadataService.MetadataPort createService() { MetadataService.MetadataPort service = new MetadataService.MetadataPort(); service.SessionHeader = new MetadataService.SessionHeader_element(); diff --git a/dlrs/main/classes/RollupControllerTest.cls b/dlrs/main/classes/RollupControllerTest.cls index 09d44f89..d22f553b 100644 --- a/dlrs/main/classes/RollupControllerTest.cls +++ b/dlrs/main/classes/RollupControllerTest.cls @@ -32,8 +32,7 @@ private class RollupControllerTest { return; System.runAs(setupTestUser()) { - // Metadata API web Service mock implementation for tests - Test.setMock(WebServiceMock.class, new WebServiceMockImpl()); + setupMocks(); // Test data LookupRollupSummary__c rollupSummaryA = new LookupRollupSummary__c(); @@ -94,52 +93,52 @@ private class RollupControllerTest { ); System.assertEquals( '' + - '' + - '' + - RollupController.API_VERSION + - '' + - '', + '' + + '' + + controller.componentApiVersion + + '' + + '', controller.getPackageXml() ); System.assertEquals( '' + - '' + - '' + - '' + - controller.RollupTriggerName + - '' + - 'ApexTrigger' + - '' + - '' + - '' + - controller.RollupTriggerTestName + - '' + - 'ApexClass' + - '' + - '' + - RollupController.API_VERSION + - '' + - '', + '' + + '' + + '' + + controller.RollupTriggerName + + '' + + 'ApexTrigger' + + '' + + '' + + '' + + controller.RollupTriggerTestName + + '' + + 'ApexClass' + + '' + + '' + + controller.componentApiVersion + + '' + + '', controller.getDestructiveChangesXml() ); System.assertEquals( '' + - '' + - '' + - RollupController.API_VERSION + - '' + - 'Active' + - '', + '' + + '' + + controller.componentApiVersion + + '' + + 'Active' + + '', controller.getTriggerCodeMetadata() ); System.assertEquals( '' + - '' + - '' + - RollupController.API_VERSION + - '' + - 'Active' + - '', + '' + + '' + + controller.componentApiVersion + + '' + + 'Active' + + '', controller.getTriggerTestCodeMetadata() ); @@ -168,8 +167,7 @@ private class RollupControllerTest { return; System.runAs(setupTestUser()) { - // Metadata API web Service mock implementation for tests - Test.setMock(WebServiceMock.class, new WebServiceMockImpl()); + setupMocks(); // Test data LookupRollupSummary__c rollupSummary = new LookupRollupSummary__c(); @@ -197,96 +195,96 @@ private class RollupControllerTest { System.assertEquals(null, controller.RollupTriggerTest); System.assertEquals( '/**\n' + - ' * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)\n' + - ' **/\n' + - 'trigger ' + - controller.RollupTriggerName + - ' on ' + - rollupSummary.ChildObject__c + - '\n' + - ' (before delete, before insert, before update, after delete, after insert, after undelete, after update)\n' + - '{\n' + - ' ' + - Utilities.classPrefix() + - 'RollupService.triggerHandler(Contact.SObjectType);\n' + - '}\n', + ' * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)\n' + + ' **/\n' + + 'trigger ' + + controller.RollupTriggerName + + ' on ' + + rollupSummary.ChildObject__c + + '\n' + + ' (before delete, before insert, before update, after delete, after insert, after undelete, after update)\n' + + '{\n' + + ' ' + + Utilities.classPrefix() + + 'RollupService.triggerHandler(Contact.SObjectType);\n' + + '}\n', controller.getTriggerCode() ); System.assertEquals( '/**\n' + - ' * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)\n' + - ' **/\n' + - '@IsTest\n' + - 'private class ' + - controller.RollupTriggerTestName + - '\n' + - '{\n' + - ' @IsTest\n' + - ' private static void testTrigger()\n' + - ' {\n' + - ' // Force the ' + - controller.RollupTriggerName + - ' to be invoked, fails the test if org config or other Apex code prevents this.\n' + - ' ' + - Utilities.classPrefix() + - 'RollupService.testHandler(new ' + - rollupSummary.ChildObject__c + - '());\n' + - ' }\n' + - '}', + ' * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)\n' + + ' **/\n' + + '@IsTest\n' + + 'private class ' + + controller.RollupTriggerTestName + + '\n' + + '{\n' + + ' @IsTest\n' + + ' private static void testTrigger()\n' + + ' {\n' + + ' // Force the ' + + controller.RollupTriggerName + + ' to be invoked, fails the test if org config or other Apex code prevents this.\n' + + ' ' + + Utilities.classPrefix() + + 'RollupService.testHandler(new ' + + rollupSummary.ChildObject__c + + '());\n' + + ' }\n' + + '}', controller.getTriggerTestCode() ); System.assertEquals( '' + - '' + - '' + - '' + - controller.RollupTriggerName + - '' + - 'ApexTrigger' + - '' + - '' + - '' + - controller.RollupTriggerTestName + - '' + - 'ApexClass' + - '' + - '' + - '' + - controller.RollupParentTriggerName + - '' + - 'ApexTrigger' + - '' + - '' + - '' + - controller.RollupParentTriggerTestName + - '' + - 'ApexClass' + - '' + - '' + - RollupController.API_VERSION + - '' + - '', + '' + + '' + + '' + + controller.RollupTriggerName + + '' + + 'ApexTrigger' + + '' + + '' + + '' + + controller.RollupTriggerTestName + + '' + + 'ApexClass' + + '' + + '' + + '' + + controller.RollupParentTriggerName + + '' + + 'ApexTrigger' + + '' + + '' + + '' + + controller.RollupParentTriggerTestName + + '' + + 'ApexClass' + + '' + + '' + + controller.componentApiVersion + + '' + + '', controller.getPackageXml() ); System.assertEquals( '' + - '' + - '' + - RollupController.API_VERSION + - '' + - 'Active' + - '', + '' + + '' + + controller.componentApiVersion + + '' + + 'Active' + + '', controller.getTriggerCodeMetadata() ); System.assertEquals( '' + - '' + - '' + - RollupController.API_VERSION + - '' + - 'Active' + - '', + '' + + '' + + controller.componentApiVersion + + '' + + 'Active' + + '', controller.getTriggerTestCodeMetadata() ); @@ -300,8 +298,7 @@ private class RollupControllerTest { return; System.runAs(setupTestUser()) { - // Metadata API web Service mock implementation for tests - Test.setMock(WebServiceMock.class, new WebServiceMockImpl()); + setupMocks(); // Test data LookupRollupSummary__c rollupSummary = new LookupRollupSummary__c(); @@ -328,19 +325,19 @@ private class RollupControllerTest { ); System.assertEquals( '/**\n' + - ' * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)\n' + - ' **/\n' + - '@IsTest\n' + - 'private class ' + - controller.RollupTriggerTestName + - '\n' + - '{\n' + - ' @IsTest(SeeAllData=true)\n' + - ' private static void testTrigger()\n' + - ' {\n' + - 'System.assertEquals(1,1);\n' + - ' }\n' + - '}', + ' * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)\n' + + ' **/\n' + + '@IsTest\n' + + 'private class ' + + controller.RollupTriggerTestName + + '\n' + + '{\n' + + ' @IsTest(SeeAllData=true)\n' + + ' private static void testTrigger()\n' + + ' {\n' + + 'System.assertEquals(1,1);\n' + + ' }\n' + + '}', controller.getTriggerTestCode() ); } @@ -352,8 +349,7 @@ private class RollupControllerTest { return; System.runAs(setupTestUser()) { - // Metadata API web Service mock implementation for tests - Test.setMock(WebServiceMock.class, new WebServiceMockImpl()); + setupMocks(); // Custom Metadata test data LookupRollupSummary2__mdt rollupSummary = new LookupRollupSummary2__mdt(); @@ -428,24 +424,116 @@ private class RollupControllerTest { ); System.assertEquals( '/**\n' + - ' * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)\n' + - ' **/\n' + - '@IsTest\n' + - 'private class ' + - controller.RollupParentTriggerTestName + - '\n' + - '{\n' + - ' @IsTest(SeeAllData=true)\n' + - ' private static void testTrigger()\n' + - ' {\n' + - 'System.assertEquals(1,1);\n' + - ' }\n' + - '}', + ' * Auto Generated and Deployed by the Declarative Lookup Rollup Summaries Tool package (dlrs)\n' + + ' **/\n' + + '@IsTest\n' + + 'private class ' + + controller.RollupParentTriggerTestName + + '\n' + + '{\n' + + ' @IsTest(SeeAllData=true)\n' + + ' private static void testTrigger()\n' + + ' {\n' + + 'System.assertEquals(1,1);\n' + + ' }\n' + + '}', controller.getParentTriggerTestCode() ); } } + @IsTest + static void testSuccessGetLatestApiVersion() { + System.runAs(setupTestUser()) { + setupMocks(); + + LookupRollupSummary2__mdt rollupSummary = new LookupRollupSummary2__mdt( + ParentObject__c = 'Account', + ChildObject__c = 'Contact' + ); + + // Open test context, permits callouts following DML + Test.startTest(); + + // Assert initial state of controller when the trigger for the child object is deployed + RollupController controller = new RollupController( + new RollupSummary(rollupSummary) + ); + Test.stopTest(); + Assert.areEqual('97.0', controller.componentApiVersion); + } + } + + @IsTest + static void testFail1GetLatestApiVersion() { + System.runAs(setupTestUser()) { + // service/data mock for getting API version + HttpCalloutMockImpl mockCallout = new HttpCalloutMockImpl(); + + HTTPResponse res = new HTTPResponse(); + // choosing to provide very high defaults so we can overcome the fallback value set in the class + res.setBody('{"success":"false"}'); + res.setHeader('Content-Type', 'application/json'); + res.setStatusCode(401); + mockCallout.responses.add(res); + + Test.setMock(HttpCalloutMock.class, mockCallout); + Test.setMock(WebServiceMock.class, new WebServiceMockImpl()); + LookupRollupSummary2__mdt rollupSummary = new LookupRollupSummary2__mdt( + ParentObject__c = 'Account', + ChildObject__c = 'Contact' + ); + + // Open test context, permits callouts following DML + Test.startTest(); + + // Assert initial state of controller when the trigger for the child object is deployed + RollupController controller = new RollupController( + new RollupSummary(rollupSummary) + ); + Test.stopTest(); + Assert.areEqual( + RollupController.FALLBACK_COMPONENT_API_VERSION, + controller.componentApiVersion + ); + } + } + + @IsTest + static void testFail2GetLatestApiVersion() { + System.runAs(setupTestUser()) { + // service/data mock for getting API version + HttpCalloutMockImpl mockCallout = new HttpCalloutMockImpl(); + + HTTPResponse res = new HTTPResponse(); + // choosing to provide very high defaults so we can overcome the fallback value set in the class + res.setBody('this is not json'); + res.setHeader('Content-Type', 'application/json'); + res.setStatusCode(200); + mockCallout.responses.add(res); + + Test.setMock(HttpCalloutMock.class, mockCallout); + Test.setMock(WebServiceMock.class, new WebServiceMockImpl()); + LookupRollupSummary2__mdt rollupSummary = new LookupRollupSummary2__mdt( + ParentObject__c = 'Account', + ChildObject__c = 'Contact' + ); + + // Open test context, permits callouts following DML + Test.startTest(); + + // Assert initial state of controller when the trigger for the child object is deployed + RollupController controller = new RollupController( + new RollupSummary(rollupSummary) + ); + Test.stopTest(); + Assert.areEqual( + RollupController.FALLBACK_COMPONENT_API_VERSION, + controller.componentApiVersion + ); + } + } + /** * Metadata API web service mock class for tests above **/ @@ -507,15 +595,12 @@ private class RollupControllerTest { String uniqueness = DateTime.now() + ':' + Math.random(); uniqueness += new NullPointerException().getStackTraceString(); //includes the top level test method name without having to pass it - // officially, there is no Read Only Profile anymore; - // its present for packaging org and scratch org support + // officially, there is no Read Only Profile anymore; + // its present for packaging org and scratch org support List profiles = [ SELECT id, Name FROM Profile - WHERE - Name = 'Read Only' - OR Name = 'ReadOnly' - OR Name = 'Standard User' + WHERE Name = 'Read Only' OR Name = 'ReadOnly' OR Name = 'Standard User' ORDER BY Name ASC ]; system.assert( @@ -572,4 +657,31 @@ private class RollupControllerTest { return result; } + + static void setupMocks() { + HttpCalloutMockImpl mockCallout = new HttpCalloutMockImpl(); + HTTPResponse res = new HTTPResponse(); + // choosing to provide very high API versions so we can overcome the fallback value set in the class + res.setBody( + '[{"label":"Winter \'97","url":"/services/data/v97.0","version":"97.0"},{"label":"Summer \'95","url":"/services/data/v95.0","version":"95.0"},{"label":"Spring \'96","url":"/services/data/v96.0","version":"96.0"}]' + ); + res.setHeader('Content-Type', 'application/json'); + res.setStatusCode(200); + mockCallout.responses.add(res); + Test.setMock(HttpCalloutMock.class, mockCallout); + // Mocking for the MetadataService + Test.setMock(WebServiceMock.class, new WebServiceMockImpl()); + } + + private class HttpCalloutMockImpl implements HttpCalloutMock { + public List requests = new List(); + public List responses = new List(); + public HTTPResponse respond(HTTPRequest req) { + // report the inbound request so it can be validated + requests.add(req); + // respond with the queued up responses, FIFO + // may throw an exception if we don't have any items in the list + return responses.remove(0); + } + } }