function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Maxence LEFEBVREMaxence LEFEBVRE 

Apply Service Layer Principles in Apex Trailhead - Issue submitting challenge

Hello everyone,

The challenge is the following :
 
Create a Service and Implement a Caller.
Create an Apex class that is a service that exposes a bulkified service method. The service closes and sets the reason for one or more given case records. Implement an Apex REST class that calls this service.
  • Create an Apex class called CaseService that contains a void static method called closeCases that takes a set of Case IDs and a String parameter for the close reason.
  • Create a REST Apex class called CaseCloseResource with a URL mapping /case/*/close (where * will be the Id). Implement a POST closeCase method which accepts a String reason and calls the CaseService.closeCases service method passing in the Id and close reason.
I keep getting the following error when trying to submit my solution on "Apply Service Layer Principles in Apex"
Challenge Not yet complete... here's what's wrong: 
The Apex service does not appear to be closing cases correctly with the specified reason.

My Service class is the following:
 
global with sharing class CaseService {

    global static void closeCases(List<Id> caseIds, String closingReason) {
        if (caseIds == null || caseIds.size() == 0) {
            throw new CaseServiceException('You should tell me which cases you wanna close');
        }
        
        if (closingReason == null || closingReason == '') {
            throw new CaseServiceException('I won\'t close a case without any reason');
        }
        
        List<Case> closedCases = new List<Case>();
        
        for(Id caseId : caseIds) {
            Case caseToClose = new Case(
            	Id = caseId,
                Status = 'Closed',
                Reason = closingReason
            );
            closedCases.add(caseToClose);
        }
        
        SavePoint sp = Database.setSavepoint();
        if (closedCases.size() > 0) {
            try {
                update closedCases;
            } catch (Exception e) {
                Database.rollback(sp);
                throw e;
            }
        }
    }
    
    public class CaseServiceException extends Exception {}
}

And my webservice endpoint (I can't really call it restful) is :
 
@RestResource(urlMapping='/case/*/close')
global with sharing class CaseCloseResource {
    
    @HttpPost
    global static void closeCase(String reason) {                
        Id id = RestContext.request.requestURI.substringBetween('case/', '/close');
        CaseService.closeCases(new Id[]{id}, reason);
    }
}

I tried in an anonymous code execution :
 
Id id = '5000Y000002Vc8Y';
CaseService.closeCases(new Id[]{id}, 'You shall be closed!');
And successfully got the following : 
Changed Case Reason from Installation to You shall be closed!.
Changed Status from New to Closed.

Then, using workbeng, I tried the following request :
POST /services/apexrest/case/5000Y000002Vc8m/close
Payload : {"reason" : "Please close this case too!"}

And got a 200 response from the server as well as 
Changed Case Reason from Feedback to Please close this case too!.
Changed Status from New to Closed.

That's why I am still confused on why the challenge checker is rejecting me ?
Did I miss something the challenge requested me to do ?
 
Best Answer chosen by Maxence LEFEBVRE
Maxence LEFEBVREMaxence LEFEBVRE
Hello Suley,

Thank you very much for your help.

As it happens, I mistook Set<Id> for List<Id> meaning the challenge couldn't find a method with a correct signature.
I think the Trailhead challenge validator should be more transparent in its error message :)

Final code :
 
public with sharing class CaseService {

    public static void closeCases(Set<Id> caseIds, String closingReason) {
        if (caseIds == null || caseIds.size() == 0) {
            throw new CaseServiceException('You should tell me which cases you wanna close');
        }
        
        if (closingReason == null || closingReason == '') {
            throw new CaseServiceException('I won\'t close a case without any reason');
        }
        
        List<Case> casesToClose = [SELECT Id, Status, Reason FROM Case Where Id IN :caseIds];
        List<Case> closedCases = new List<Case>();
        
        for(Case caseToClose : casesToClose) {
            caseToClose.Status = 'Closed';
            caseToClose.Reason = closingReason;
            
            closedCases.add(caseToClose);
        }
        
        SavePoint sp = Database.setSavepoint();
        if (closedCases.size() > 0) {
            try {
                update closedCases;
            } catch (Exception e) {
                Database.rollback(sp);
                throw e;
            }
        }
    }
    
    public class CaseServiceException extends Exception {}
}
 
@RestResource(urlMapping='/case/*/close')
global with sharing class CaseCloseResource {
    
    @HttpPost
    global static void closeCase(String reason) {                
        Id id = RestContext.request.requestURI.substringBetween('case/', '/close');
        CaseService.closeCases(new Set<Id>{id}, reason);
    }
}
 
@isTest
public class CaseServiceTest {

    @isTest
    public static void testCloseCasesShouldCloseCasesWithTheSpecifiedReason() {
        setupTests();
        
        Set<Id> insertedCasesId = new Set<Id>();
        for (Case c : [SELECT Id FROM Case]) {
            insertedCasesId.add(c.Id);
        }
        
        System.assertEquals(100, insertedCasesId.size());
        
        CaseService.closeCases(insertedCasesId, 'This is my test reason');
        
        System.assertEquals(100, [SELECT Id FROM Case WHERE Status = 'Closed' AND Reason = 'This is my test reason'].size());
    }
    
    @isTest
    public static void testCloseCasesShouldThrowACaseServiceExceptionWithoutCasesToClose() {
        try {
            CaseService.closeCases(new Set<Id>(), '');
            System.assert(false);
        } catch (CaseService.CaseServiceException e) {
            System.assertEquals('You should tell me which cases you wanna close', e.getMessage());
        }    
    }
    
    
    @isTest
    public static void testCloseCasesShouldThrowACaseServiceExceptionWithoutReason() {
        setupTests();
        
        Set<Id> insertedCasesId = new Set<Id>();
        for (Case c : [SELECT Id FROM Case]) {
            insertedCasesId.add(c.Id);
        }
        
        System.assertEquals(100, insertedCasesId.size());
        
        try {
            CaseService.closeCases(insertedCasesId, '');
            System.assert(false);
        } catch (CaseService.CaseServiceException e) {
            System.assertEquals('I won\'t close a case without any reason', e.getMessage());
        }   
    }
    
    private static void setupTests() {
        List<Case> testCases = new List<Case>();
        
        for (Integer i = 0; i < 100; i++) {
            testCases.add(new Case(
            	Subject='This is the test case number ' + i,
                Status='New'
            ));
        }
                
        insert testCases;
    }
}



 

All Answers

Suley KaboreSuley Kabore
Hello! It looks like you did not create an SObject to hold the records in the database whose status needs to be changed to 'closed'. You are working on the set of Ids wich provide you with only the 'ID' field so you dont have acces to the 'Status' field to change it.
Here is my code:

public with sharing class CaseService {
    public static void closeCases(set<ID> caseIDs, String newReason)
    {
        //Validate parameters
        if(caseIDs ==null || caseIDs.size() == 0)
        {
            throw new CaseServiceException('Cases not specified');
        }
        
        if(newReason == null || newReason == '')
        {
            throw new caseServiceException('Invalide reason provided');
        }
        
        //Query cases
        List<Case> casesToClose = [select Id, Status, Reason  from Case where Id IN :caseIDs];
        List<Case> updatedCases = new List<Case>();
        
        for(Case caseToUpd : casesToClose)
        {
            caseToUpd.Status = 'Closed';
            caseToUpd.Reason = newReason;
            updatedCases.add(caseToUpd);
        }
        
        //Update database
        SavePoint sp = Database.setSavepoint();
        try
        {
        update updatedCases;
        }
        catch(Exception e)
        {
            //Rollback
            Database.rollback(sp);
            //Throw expecetion on to caller
            throw e;
        }
    }
    
    public class CaseServiceException extends Exception{}

}
Maxence LEFEBVREMaxence LEFEBVRE
Hello !

Thank you very much for your answer.

I tried this way too, with the following service class :
 
global with sharing class CaseService {

    global static void closeCases(List<Id> caseIds, String closingReason) {
        if (caseIds == null || caseIds.size() == 0) {
            throw new CaseServiceException('You should tell me which cases you wanna close');
        }
        
        if (closingReason == null || closingReason == '') {
            throw new CaseServiceException('I won\'t close a case without any reason');
        }
        
        List<Case> casesToClose = [SELECT Id, Status, Reason FROM Case Where Id IN :caseIds];
        List<Case> closedCases = new List<Case>();
        
        for(Case caseToClose : casesToClose) {
            caseToClose.Status = 'Closed';
            caseToClose.Reason = closingReason;
            
            closedCases.add(caseToClose);
        }
        
        SavePoint sp = Database.setSavepoint();
        if (closedCases.size() > 0) {
            try {
                update closedCases;
            } catch (Exception e) {
                Database.rollback(sp);
                throw e;
            }
        }
    }
    
    public class CaseServiceException extends Exception {}
}

Unfortunately, the challenge still can't be validated, the same error is still returning
Challenge Not yet complete... here's what's wrong: 
The Apex service does not appear to be closing cases correctly with the specified reason.

I unit tested with the following class and the test suite is all green !
 
@isTest
public class CaseServiceTest {

    @isTest
    public static void testCloseCasesShouldCloseCasesWithTheSpecifiedReason() {
        setupTests();
        
        List<Id> insertedCasesId = new List<Id>();
        for (Case c : [SELECT Id FROM Case]) {
            insertedCasesId.add(c.Id);
        }
        
        System.assertEquals(100, insertedCasesId.size());
        
        CaseService.closeCases(insertedCasesId, 'This is my test reason');
        
        System.assertEquals(100, [SELECT Id FROM Case WHERE Status = 'Closed' AND Reason = 'This is my test reason'].size());
    }
    
    @isTest
    public static void testCloseCasesShouldThrowACaseServiceExceptionWithoutCasesToClose() {
        try {
            CaseService.closeCases(new List<Id>(), '');
            System.assert(false);
        } catch (CaseService.CaseServiceException e) {
            System.assertEquals('You should tell me which cases you wanna close', e.getMessage());
        }    
    }
    
    
    @isTest
    public static void testCloseCasesShouldThrowACaseServiceExceptionWithoutReason() {
        setupTests();
        
        List<Id> insertedCasesId = new List<Id>();
        for (Case c : [SELECT Id FROM Case]) {
            insertedCasesId.add(c.Id);
        }
        
        System.assertEquals(100, insertedCasesId.size());
        
        try {
            CaseService.closeCases(insertedCasesId, '');
            System.assert(false);
        } catch (CaseService.CaseServiceException e) {
            System.assertEquals('I won\'t close a case without any reason', e.getMessage());
        }   
    }
    
    private static void setupTests() {
        List<Case> testCases = new List<Case>();
        
        for (Integer i = 0; i < 100; i++) {
            testCases.add(new Case(
            	Subject='This is the test case number ' + i,
                Status='New'
            ));
        }
                
        insert testCases;
    }
}

 
Suley KaboreSuley Kabore
Hello! The caseService class seems fine now, but it does not need to be global but public. So I would suggest you to fix that in Case trailhead engine is checking for that. The Rest class is the one that should be global and here is my working code:

@RestResource(URLMapping = '/case/*/close')
global with sharing class CaseCloseResource {
    @HttpPost
    global static void closeCase(String closureReason)
    {
        //Parse request
        RestRequest req = RestContext.request;
        String[] idsList = req.requestURI.split('/');
        ID caseIDs = idsList[2];
        //Call the service
        CaseService.closeCases(new Set<ID> {caseIDs}, closureReason);
    }

}
Maxence LEFEBVREMaxence LEFEBVRE
Hello Suley,

Thank you very much for your help.

As it happens, I mistook Set<Id> for List<Id> meaning the challenge couldn't find a method with a correct signature.
I think the Trailhead challenge validator should be more transparent in its error message :)

Final code :
 
public with sharing class CaseService {

    public static void closeCases(Set<Id> caseIds, String closingReason) {
        if (caseIds == null || caseIds.size() == 0) {
            throw new CaseServiceException('You should tell me which cases you wanna close');
        }
        
        if (closingReason == null || closingReason == '') {
            throw new CaseServiceException('I won\'t close a case without any reason');
        }
        
        List<Case> casesToClose = [SELECT Id, Status, Reason FROM Case Where Id IN :caseIds];
        List<Case> closedCases = new List<Case>();
        
        for(Case caseToClose : casesToClose) {
            caseToClose.Status = 'Closed';
            caseToClose.Reason = closingReason;
            
            closedCases.add(caseToClose);
        }
        
        SavePoint sp = Database.setSavepoint();
        if (closedCases.size() > 0) {
            try {
                update closedCases;
            } catch (Exception e) {
                Database.rollback(sp);
                throw e;
            }
        }
    }
    
    public class CaseServiceException extends Exception {}
}
 
@RestResource(urlMapping='/case/*/close')
global with sharing class CaseCloseResource {
    
    @HttpPost
    global static void closeCase(String reason) {                
        Id id = RestContext.request.requestURI.substringBetween('case/', '/close');
        CaseService.closeCases(new Set<Id>{id}, reason);
    }
}
 
@isTest
public class CaseServiceTest {

    @isTest
    public static void testCloseCasesShouldCloseCasesWithTheSpecifiedReason() {
        setupTests();
        
        Set<Id> insertedCasesId = new Set<Id>();
        for (Case c : [SELECT Id FROM Case]) {
            insertedCasesId.add(c.Id);
        }
        
        System.assertEquals(100, insertedCasesId.size());
        
        CaseService.closeCases(insertedCasesId, 'This is my test reason');
        
        System.assertEquals(100, [SELECT Id FROM Case WHERE Status = 'Closed' AND Reason = 'This is my test reason'].size());
    }
    
    @isTest
    public static void testCloseCasesShouldThrowACaseServiceExceptionWithoutCasesToClose() {
        try {
            CaseService.closeCases(new Set<Id>(), '');
            System.assert(false);
        } catch (CaseService.CaseServiceException e) {
            System.assertEquals('You should tell me which cases you wanna close', e.getMessage());
        }    
    }
    
    
    @isTest
    public static void testCloseCasesShouldThrowACaseServiceExceptionWithoutReason() {
        setupTests();
        
        Set<Id> insertedCasesId = new Set<Id>();
        for (Case c : [SELECT Id FROM Case]) {
            insertedCasesId.add(c.Id);
        }
        
        System.assertEquals(100, insertedCasesId.size());
        
        try {
            CaseService.closeCases(insertedCasesId, '');
            System.assert(false);
        } catch (CaseService.CaseServiceException e) {
            System.assertEquals('I won\'t close a case without any reason', e.getMessage());
        }   
    }
    
    private static void setupTests() {
        List<Case> testCases = new List<Case>();
        
        for (Integer i = 0; i < 100; i++) {
            testCases.add(new Case(
            	Subject='This is the test case number ' + i,
                Status='New'
            ));
        }
                
        insert testCases;
    }
}



 
This was selected as the best answer
Suley KaboreSuley Kabore
Your welcome Maxence, and I  am glad you finally passed it.I agree with you. Sometimes, it is more difficult to understand the error message, that to to the challenge itself. Have a great one  Sent from Yahoo Mail on Android
Charles StevensonCharles Stevenson
Thanks Suley and Maxence - I made the same mistake using a list<id> instead of set<id>
KapavariVenkatramanaKapavariVenkatramana
Realy awesome code WOrked effienciently.Thanks a lot.