Apex Specialist Superbadge

Step 1: Automate record creation

Install the unmanaged package for the schema and stubs for Apex classes and triggers. Rename cases and products to match the HowWeRoll schema, and assign all profiles to the custom HowWeRoll page layouts for those objects. Use the included package content to automatically create a Routine Maintenance request every time a maintenance request of type Repair or Routine Maintenance is updated to Closed. Follow the specifications and naming conventions outlined in the business requirements.

public class MaintenanceRequestHelper {
    
    // trigerhandler
    public static void trigHandler (List<Case> newTrig, List<Case> oldTrig,System.TriggerOperation trigEnum)
    {
        Map<Id,Case> finalRecordtoUpdate = new Map<Id,Case>();
        Switch on trigEnum
        {
            when AFTER_UPDATE
            {
                for(Case c : newTrig)//loop through trigger.newList
                {
                    if(c.IsClosed && (c.Type=='Repair'||c.Type=='Routine Maintenance'))
                    {
                        finalRecordtoUpdate.put(c.Id,c);
                    }
                }
                if(!finalRecordtoUpdate.values().isEmpty())
                //pass the processed finalRecordtoUpdate collection
                createScheduledMaintenance(finalRecordtoUpdate);
            }
          
        }
        
    }
    
    public static void createScheduledMaintenance (Map<id,Case> extractedCase)
    {
        // collects installed equipments from Intallation Case records.
        List<Case> NewCases = new List<Case>();
        List<Case> updateNewCase = extractedCase.values();
        Map<String,Integer> dueDate = getDueDatePerCase(updateNewCase);
        for (Case c : updateNewCase)
        {
            
				Case newRCase = new Case ();
            	newRCase.Vehicle__c = c.Vehicle__c;
                newRCase.Origin='web';
                newRCase.AccountId  = c.AccountId;
            	newRCase.Status     = 'New';
                newRCase.Subject    = 'Routine Maintenance of Vehicle';
                newRCase.Date_Reported__c = Date.today();  // last check
                newRCase.Type       = 'Routine Maintenance';
                newRCase.Equipment__c = c.Equipment__c;
            	if(dueDate.get(c.Id) != null)
                   {
                       newRCase.Date_Due__c= Date.today()+dueDate.get(c.Id); // recommandated date
                    }
            
             NewCases.add(newRCase);
        }
        insert NewCases;
    }
    
    // Pass one Work_Part__c record that has the shoortest maintanance Cycle.
    private static Map<String,Integer> getDueDatePerCase (List<Case> cycleCases)
    {
        Map<String,Integer> addedDays = new Map<String,Integer>();
        Map<Id, Case> caseKeys = new Map<Id, Case>(cycleCases);
        List<AggregateResult> allWorkParts = [SELECT MIN(Equipment__r.Maintenance_Cycle__c)mcycle,maintenance_Request__r.Id //mcycle is alias
                                               FROM Work_Part__C
                                               WHERE Maintenance_Request__r.Id IN :caseKeys.KeySet()
                                               AND Equipment__r.Maintenance_Cycle__c !=0
                                               GROUP By Maintenance_Request__r.Id ];
        for(AggregateResult ag : allWorkParts )
        {
            Integer mcdays = 0;
            if(ag.get('mcycle') != null)
            {
                mcdays += Integer.valueOf(ag.get('mcycle'));
                addedDays.put((String)ag.get('c.Id'),mcdays);
            }
        }
        return addedDays;
    }
}
trigger MaintenanceRequest on Case (before update, after update) {

        if(Trigger.isAfter)
        {
            //pass trigger old and trigger new
            MaintenanceRequestHelper.trigHandler(Trigger.New,Trigger.Old,Trigger.operationType);
          
        }}

Step 2: Synchronize Salesforce data with an external system

Implement an Apex class (called WarehouseCalloutService) that uses the @future annotation and makes a callout to an external service used for warehouse inventory management. This service receives updated values in the external system and update the related records in Salesforce. Before checking this section, run the WarehouseCalloutService.runWarehouseEquipmentSync() method at least once, confirming that it’s working as expected.

public class WarehouseCalloutService {

    private static List<Equip_Object> equipObjects;
    
    @future(callout=true)
    public static void runWarehouseEquipmentSync() {
        //ToDo: complete this method to make the callout (using @future) to the
        //      REST endpoint and update equipment on hand.
        httpCallout();
        mapToEquipment();
                
    }
    
    public static HttpResponse httpCallout() // get JSON strings
    {
        Http http = new Http();
        HttpRequest req = new HttpRequest();
        req.setEndpoint('https://th-superbadge-apex.herokuapp.com/equipment');
        req.setMethod('GET');
        req.setHeader('Content-Type', 'application/json;charset=UTF-8');
        
        HTTPResponse res = http.send(req);
        System.debug(res.getBody());
        
        if(res.getStatusCode() == 200)
        {
            equipObjects = (List<Equip_Object>)JSON.deserialize(res.getBody(), List<Equip_Object>.class);
        }
    
        return res;
    }
    
    // get key
    // map Equip_object to Product2
    @TestVisible
    private static void mapToEquipment()
    {
        Map<String,Equip_Object> skuKeySet = new Map<String,Equip_Object>();
        for(Equip_Object eq :equipObjects)
        {
           if(eq.sku != null)
           {
               skuKeySet.put(eq.sku,eq);
           }
        }
        
        List<Product2> upsertProduct2 = new List<Product2>();
        List<Product2> queryEquipments =[SELECT id,Warehouse_SKU__c ,Replacement_Part__c,Current_Inventory__c, 
                                         Name,Maintenance_Cycle__c,Lifespan_Months__c,Cost__c 
                                         FROM Product2
                                         WHERE Warehouse_SKU__c  IN:skuKeySet.KeySet()];
       for(Product2 p :queryEquipments)
        {
           
          if(skuKeySet.KeySet().contains(p.Warehouse_SKU__c)) // for update
          {
            p.Warehouse_SKU__c 		= skuKeySet.get(p.Warehouse_SKU__c).sku;
            p.Replacement_Part__c 	= skuKeySet.get(p.Warehouse_SKU__c).replacement;
        	p.Current_Inventory__c 	= skuKeySet.get(p.Warehouse_SKU__c).quantity;
        	p.Name					= skuKeySet.get(p.Warehouse_SKU__c).name;
        	p.Maintenance_Cycle__c	= skuKeySet.get(p.Warehouse_SKU__c).maintenanceperiod;
            p.Lifespan_Months__c 	= skuKeySet.get(p.Warehouse_SKU__c).lifespan;
        	p.Cost__c				= skuKeySet.get(p.Warehouse_SKU__c).cost;
            
            upsertProduct2.add(p);
            skuKeySet.remove(p.Warehouse_SKU__c );
              
          }
        }
        List<Equip_Object> eq = skuKeySet.values();
        for(Equip_Object e :eq)
          {
            Product2 p = new Product2();
            p.Warehouse_SKU__c 		= e.sku;
            p.Replacement_Part__c 	= e.replacement;
        	p.Current_Inventory__c 	= e.quantity;
        	p.Name					= e.name;
        	p.Maintenance_Cycle__c	= e.maintenanceperiod;
            p.Lifespan_Months__c 	= e.lifespan;
        	p.Cost__c				= e.cost;
              
            upsertProduct2.add(p);
           }
        
        upsert upsertProduct2;
       }
    
    
    
    @TestVisible    
    public class Equip_Object 
    {
        public String sku;
        public Boolean replacement;
        public Integer quantity;
        public String name;
        public Integer maintenanceperiod;
        public Integer lifespan;
        public Double cost;
        
        public Equip_Object(String sku1,Boolean replacement1,Integer quantity1,String name1,Integer maintenanceperiod1,Integer lifespan1,Double cost1)
        {
            
            
            sku 		= sku1;
        	replacement = replacement1;
        	quantity	= quantity1;
        	name		= name1;
        	maintenanceperiod	= maintenanceperiod1;
            lifespan	= lifespan1;
        	cost		= cost1;
        }
 	}
}
// I want to use custom object to map fields.
//JSON.tostring
// HTTP class (request adn response)
// fields
/*Your class maps the following fields: 
replacement part (always true), 
cost, current inventory, 
lifespan, 
maintenance cycle, 
and warehouse SKU. 

*/
//You need to update Salesforce data during off hours 
//(at 1:00 AM PST).

Step 3: Schedule synchronization

Build scheduling logic that executes your callout and runs your code daily. Schedule this service, called WarehouseSyncSchedule, as specified in the business requirements.

global class WarehouseSyncSchedule implements Schedulable {
    // implement scheduled code here
  
    global void execute(SchedulableContext sc)
    {
        WarehouseCalloutService.runWarehouseEquipmentSync();
    }
}


//WarehouseSyncSchedule w = new WarehouseSyncSchedule();
//String sch='0 0 1 * * ?';
//String jobID = system.schedule('call JSON Job', sch, w);

Step 5-7: Unit Test

Write a unit test for Routine Maintenance Request automation, WarehouseCalloutService (Http callout), and Future callout.

InstallationTests.cls
// This class can be safely deleted from your org. 
// It was only used to ensure 100% test coverage 
// for the managed package installation 

@IsTest
public class InstallationTests {
     // invoke DataFactoryTest class
    private static DataFactoryTest df = new DataFactoryTest();
    
    @TestSetup
    Static void testDateFactory()
    {
        // create a batch of 300 equipment records (Product2)
        List <Product2> testequip = new List <Product2>();
        Product2 mr365 = df.equipInventory('equipMR356', '125', True, 10, 365, 120, 500);
        Product2 mr60 = df.equipInventory('equipMR60', '345', True, 10, 60, 70, 100);
        Product2 mr0 = df.equipInventory('Generator 1000 kW', '100003', False, 10, 365, 120, 500);
        
        testequip.addAll(new List<Product2>{mr365,mr60,mr0});
        insert testequip; // insert new equipments
        // check 3 items
        System.assertEquals(3, [SELECT count() from Product2], 'Insertion failed');
        
        // create a batch of 300 records that will be updated to "Closed"
        // Case1: Maintenance Request with multiple work parts (100)
        // Case2: Maintenance REquest with work parts without maintenance  (100)
        // Case3: Maintenance Request with Type !='Routine Maintenance' (100)
        // create a work_part__c records and equipment  	   
       List<Case> newTestCasetoClose = df.closeMR();
       
       insert newTestCasetoClose; // insert new cases
	
       
       Integer count = 0;
       List<Work_Part__c> wpAttach = new List<Work_Part__c>();
       List<Case> updateTypeCasetoClose = new List<Case>(); // new collection to hold updated Type
       for(Case c : newTestCasetoClose)
       {
           if(count <150)
           {
            c.Type       = 'Routine Maintenance';
            Work_Part__c wpmr365 = df.workpart (testequip[0].Id,c.Id);
            Work_Part__c wpmr60 = df.workpart (testequip[1].Id,c.Id);
            
            wpAttach.addAll(new List <Work_Part__c>{wpmr365,wpmr60});
            updateTypeCasetoClose.add(c);
           }
           if(count >149 && count <300)
           {
            c.Type       = 'Installation';
            Work_Part__c wpmr365_1 = df.workpart (testequip[0].Id,c.Id);
            Work_Part__c wpmr60_1 = df.workpart (testequip[1].Id,c.Id);
            
            wpAttach.addAll(new List <Work_Part__c>{wpmr365_1,wpmr60_1});
            updateTypeCasetoClose.add(c);
           }
            count = count+1;
       }
        insert wpAttach;
        update updateTypeCasetoClose;
        //close the case
        List<Case> getallCases=[SELECT id,Status FROM Case];
        for(Case c :getallCases)
        {
            if(c.Status=='New')
            {
                c.Status='Closed';
            }
        }
        update getallCases;
    }
    
    
    @IsTest
    public static void testMaintenanceRequest(){
        MaintenanceRequestHelper.updateWorkOrders();
        system.assertEquals(150, [SELECT count() FROM Case WHERE Type='Routine Maintenance' AND Status='New']);
        
        List <Case> dueDate = [SELECT id,vehicle__c FROM Case WHERE Type='Routine Maintenance' AND Status='Closed'LIMIT 1];
        List <Case> newdateDue = [SELECT Id, Date_Due__c FROM Case WHERE Type='Routine Maintenance'AND Status='New' AND vehicle__c=:dueDate[0].vehicle__c];
        system.debug('AGG: '+newdateDue);
        Map<String,Integer> dueDateCal = new Map<String,Integer>();
        List<AggregateResult> equipMC = [SELECT MIN(Equipment__r.Maintenance_Cycle__c)mcycle,maintenance_Request__r.Id cId //mcycle is alias
                                               FROM Work_Part__C
                                               WHERE Maintenance_Request__r.Id = :dueDate[0].Id
                                               GROUP By Maintenance_Request__r.Id ];
        for(AggregateResult ag : equipMC )
        {
            Integer mcdays = 0;
            if(ag.get('mcycle') != null)
            {
                mcdays += Integer.valueOf(ag.get('mcycle'));
                dueDateCal.put((String)ag.get('cId'),mcdays);
            }
        }
       
        system.assertEquals(Date.today()+dueDateCal.get(dueDate[0].Id),newdateDue[0].Date_Due__c,'Due Date is not: '+Date.today()+60);
    	
    }
    
}
WarehouseCalloutServiceTest.cls
@IsTest
private class WarehouseCalloutServiceTest {
    // implement your mock callout test here
    private static DataFactoryTest df = new DataFactoryTest();
    public static String CRON_EXP = '0 0 1 * * ?';
    public static List<Product2> upsertProduct2 = new List<Product2>();
    
    @isTest
    Static void schedulableTest()
    {
        	Test.setMock(HttpCalloutMock.class,new WarehouseCalloutServiceMock());
        	Test.startTest();
            String jobId = System.schedule('WarehouseSyncScheduleApexTest',
            CRON_EXP, 
            new WarehouseSyncSchedule());
        	Test.stopTest();
        }
    
	@IsTest
    public static void testWarehouseSync(){
       
  // associate the callout with a mock response
        Test.setMock(HttpCalloutMock.class,new WarehouseCalloutServiceMock());
        
        // call method to test
        HttpResponse result = WarehouseCalloutService.httpCallout();
        //verify mock reponse is not null
        System.assertNotEquals(null,result,'The callout returned a null response.');
        // verify status code
        System.assertEquals(200,result.getStatusCode(),'The status code is not 200/successful');
        // verify content type
        System.assertEquals('application/json;charset=UTF-8',result.getHeader('Content-Type'),'The content type value is not expected.');
        // verify body content
        System.assertEquals('[{"_id":"55d66226726b611100aaf741","replacement":false,"quantity":5,"name":"Generator 1000 kW","maintenanceperiod":365,"lifespan":120,"cost":5000,"sku":"100003"},{"_id":"55d66226726b611100aaf742","replacement":true,"quantity":183,"name":"Cooling Fan","maintenanceperiod":0,"lifespan":0,"cost":300,"sku":"100004"},{"_id":"55d66226726b611100aaf74d","replacement":false,"quantity":8,"name":"UPS 3000 VA","maintenanceperiod":180,"lifespan":60,"cost":1600,"sku":"100015"}]',result.getBody(),'String values are not expected.');
    		         
        List <Product2> testequip = new List <Product2>();
        Product2 mr365 = df.equipInventory('equipMR356', '125', True, 10, 365, 120, 500);
        Product2 mr60 = df.equipInventory('equipMR60', '345', True, 10, 60, 70, 100);
        Product2 mr0 = df.equipInventory('Generator 1000 kW', '100003', False, 10, 365, 120, 500);
        
        testequip.addAll(new List<Product2>{mr365,mr60,mr0});
        insert testequip; 
        
        System.assertEquals(3,[SELECT count() FROM Product2],'There is '+[SELECT count() FROM Product2]);

        List<WarehouseCalloutService.Equip_Object> equipList = new List<WarehouseCalloutService.Equip_Object>();
       // WarehouseCalloutService.Equip_Object eq = new WarehouseCalloutService.Equip_Object();
       // deserialize each JSON object into a Equip_Object
		equipList = (List<WarehouseCalloutService.Equip_Object>)JSON.deserialize(result.getBody(), List<WarehouseCalloutService.Equip_Object>.class);         	
            
        System.assertEquals(equipList[0].sku,'100003','Wrong SKU');//check mapped value
        
        
        WarehouseCalloutService.mapToEquipment();
        
        
        system.assertEquals(5,[SELECT Current_Inventory__c FROM Product2 WHERE Warehouse_SKU__c ='100003'].Current_Inventory__c,'This product deson\'t exist');
		system.assertEquals('100004',[SELECT id, Warehouse_SKU__c FROM Product2 WHERE Warehouse_SKU__c ='100004'].Warehouse_SKU__c,'Not 100004');
 		system.assertEquals('100015',[SELECT id, Warehouse_SKU__c FROM Product2 WHERE Warehouse_SKU__c ='100015'].Warehouse_SKU__c,'Not 100004');
		system.assertEquals(5,[SELECT count() FROM Product2],'These is no 5 items');
        
        
    }
    
static testMethod void buildData()
    	{
        	Test.startTest();
            WarehouseCalloutService.Equip_Object eq = new WarehouseCalloutService.Equip_Object('123',False,10,'Generator',360,120,5000);
            Test.stopTest();
            system.assertEquals('123',eq.sku,'Wrong Warehouse SKU #');
    	}
    
}
WarehouseCalloutServiceMock.cls
@isTest
global class WarehouseCalloutServiceMock implements HttpCalloutMock {
    // implement http mock callout
    global HTTPResponse respond(HTTPRequest req)
    {   // create a fake response
// Optionally, only send a mock response for a specific endpoint
        // and method.
        System.assertEquals('https://th-superbadge-apex.herokuapp.com/equipment', req.getEndpoint());
        System.assertEquals('GET', req.getMethod());
        
        // Create a fake response
        HttpResponse res = new HttpResponse();
        res.setHeader('Content-Type', 'application/json;charset=UTF-8');
        res.setBody('[{"_id":"55d66226726b611100aaf741","replacement":false,"quantity":5,"name":"Generator 1000 kW","maintenanceperiod":365,"lifespan":120,"cost":5000,"sku":"100003"},{"_id":"55d66226726b611100aaf742","replacement":true,"quantity":183,"name":"Cooling Fan","maintenanceperiod":0,"lifespan":0,"cost":300,"sku":"100004"},{"_id":"55d66226726b611100aaf74d","replacement":false,"quantity":8,"name":"UPS 3000 VA","maintenanceperiod":180,"lifespan":60,"cost":1600,"sku":"100015"}]');
        res.setStatusCode(200);
        
        return res;
    }
}
References
  1. Testing HTTP Callouts by Implementing the HttpCalloutMock Interface
  2. @TestVisible annotation
  3. Differences Between Apex Classes and Java Classes
  4. Using Constructor
  5. Trigger Class: Trigger.OperationType – Possible values of the System.TriggerOperation enum are: BEFORE_INSERTBEFORE_UPDATEBEFORE_DELETE,AFTER_INSERTAFTER_UPDATEAFTER_DELETE, and AFTER_UNDELETE. If you vary your programming logic based on different trigger types, consider using the switch statement with different permutations of unique trigger execution enum states.
  6. SOQL Alias notations
  7. Query Result Types
  8. Switch Statement
Testing an inner class with the constructor:

I made the inner class Equip_Object @Testvisible and instantiate in a test class.

static testMethod void buildData()

{

Test.startTest();

WarehouseCalloutService.Equip_Object eq = new WarehouseCalloutService.Equip_Object(‘123′,False,10,’Generator’,360,120,5000);

Test.stopTest();

system.assertEquals(‘123′,eq.sku,’Wrong Warehouse SKU #’);

}