0

"Hello everyone" I have a week with this problem in the testClass. So the problem is the following: System.QueryException: List has no rows for assignment to SObject json.

The problem comes when epic the json method that has JSON Generator function and it tries to cover in test class (Controller class).

If you can see, the first methods 'postProjects' and the first ones, im getting succesfull in the test class. Also is getting their json method because is more like a simple string. But in postEpicIssues method for example, it is calling his jsonMethod String with JSONGenerator function, so I dont know what exactly do.

The json is more complex because it has some loops to expand the json String, maybe I could try to change the json string with another fuction, more simple. But the work is done. So, if someone have idea how to cover the jsonGenerator, I would be grateful. :)

I commented the setBody line in postEpicIssues method and it was covered totally(no json method ofc), but you know what is the Idea. That is what I'm sure that jsonGenerator is the problem.

Controller Class

public with sharing class JiraIntegrationController{
    ///////////////////  POST/GET METHODS   ////////////////////
    @AuraEnabled(cacheable=true)
    public static Integer postProject(String idOpportunity){
        Integer valuestatuscode;
        HTTPRequest feedRequest = new HTTPRequest();
        feedRequest.setEndpoint('callout:Jira/rest/api/2/project');
        feedRequest.setHeader('Content-Type', 'application/json;charset=UTF-8');
        feedRequest.setMethod('POST');
        feedRequest.setBody(JSONProject(idOpportunity));
        
        HTTP http = new HTTP();
        HTTPResponse feedResponse = new HTTPResponse();
        try{
            feedResponse = http.send(feedRequest);
            Integer statusCode = feedResponse.getStatusCode();
            if(statusCode==201){
                valuestatuscode = 201;
                System.debug(feedResponse.getBody());
                String IdProjectJira = feedResponse.getBody();
                System.debug(IdProjectJira);
                JSONParser parser = JSON.createParser(IdProjectJira);
                String id = '';
                while (parser.nextToken() != null) {
                    if ((parser.getCurrentToken() == JSONToken.FIELD_NAME) && 
                        (parser.getText() == 'id')) {
                        parser.nextToken();
                        id += parser.getIntegerValue();
                    }
                }   
                String finalIdProjectJira = id;
                postEpicsIssues(idOpportunity, finalIdProjectJira);
            }
            else if(statusCode==400 || statusCode == 500){
                valuestatuscode = 400;
                System.debug('Ya existe un proyecto con ese nombre - Hay un proyecto que utiliza esta clave de proyecto');
            }
        }catch(System.CalloutException e) {
            System.debug('Callout error: '+ e);
        } 
        return valuestatuscode;
    }
    
    public static List<Project__c> getProjectByOpportunityId(String idOpportunity){
        Opportunity opp = [SELECT Name, Project__r.Id FROM Opportunity WHERE Id =: idOpportunity LIMIT 1];
        String IdProject = opp.Project__r.Id;
        
        List<Project__c> project = [SELECT Name, Scope__c, Key__c, ScrumMaster__c, Sprints__c FROM Project__c WHERE Id =: IdProject];
        return project;
    }   
    
    public static String postEpicsIssues(String oppid, String idproject){
        HTTPRequest feedRequest = new HTTPRequest();
        feedRequest.setEndpoint('callout:Jira/rest/api/2/issue/bulk');
        feedRequest.setHeader('Content-Type', 'application/json;charset=UTF-8');
        feedRequest.setMethod('POST');
        feedRequest.setBody(JSONEpics(oppid, idproject));
             
        HTTP http = new HTTP();
        try{
            HTTPResponse feedResponse = http.send(feedRequest);
            Integer statusCode = feedResponse.getStatusCode();
            if(statusCode==201){
                String preresponse = feedResponse.getBody();
                List<String> ids = new List<String>();
                String idn;
                JSONParser parser = JSON.createParser(preresponse);
                while (parser.nextToken() != null) {
                    if(parser.getCurrentName() == 'key') {
                        parser.nextValue();
                        ids.add(parser.getText());
                    }
                }
                postHistoryIssues(oppid, idproject, ids);
            }else{
                System.debug('No se han creado Epic Issues correctamente');
            }
        }catch(Exception ex){
            System.debug('Error--'+ex.getMessage());
        }
        return null;
    }
    
    public static String postHistoryIssues(String oppid, String idproject, List<String> lstkeyss){
        HTTPRequest feedRequest = new HTTPRequest();
        feedRequest.setEndpoint('callout:Jira/rest/api/2/issue/bulk');
        feedRequest.setHeader('Content-Type', 'application/json;charset=UTF-8');
        feedRequest.setMethod('POST');
        feedRequest.setBody(JSONHistory(oppid, idproject, lstkeyss));
             
        HTTP http = new HTTP();
        try{
            HTTPResponse feedResponse = http.send(feedRequest);
            Integer statusCode = feedResponse.getStatusCode();
            if(statusCode==201){
                System.debug('Se han creado Issue History Correctamente');
                System.debug(feedResponse.getBody());
                sendSprintsByFieldValue(oppid, idproject);
            }
            else{
                System.debug('No se ha creado ningun Issue History');
            }
        }catch(Exception ex){
            System.debug('Error--'+ex.getMessage());
        }
        return null;
    }
    
    public static String getBoardId(String idProject){
        String originBoardId;
        HTTPRequest feedRequest = new HTTPRequest();
        Http http = new Http();
        feedRequest.setEndpoint('callout:Jira/rest/agile/1.0/board?projectKeyOrId=' + idProject );
        feedRequest.setHeader('Content-Type', 'application/json;charset=UTF-8');
        feedRequest.setMethod('GET');
        HttpResponse response = http.send(feedRequest);
        String jsonResponse = response.getBody();
        Integer statusCode = response.getStatusCode();
        System.debug('Status code: ' + statusCode);
        
        if(statusCode == 200){
            List<String> ids = new List<String>();
            JSONParser parser = JSON.createParser(jsonResponse);
            while (parser.nextToken() != null) {
                if(parser.getCurrentName() == 'id') {
                    parser.nextValue();
                    ids.add(parser.getText());
                }
            }  
            originBoardId = ids[0];
        }
        System.debug('Id fuera del if : ' + statusCode);
        return originBoardId;
    }
    
    public static String postSprints(String idProject, Integer i){
        HTTPRequest feedRequest = new HTTPRequest();
        feedRequest.setEndpoint('callout:Jira/rest/agile/1.0/sprint');
        feedRequest.setHeader('Content-Type', 'application/json;charset=UTF-8');
        feedRequest.setMethod('POST');
        feedRequest.setBody(JSONSprint(getBoardId(idProject), i));
        
        HTTP http = new HTTP();
        try{
            HTTPResponse feedResponse = http.send(feedRequest);
            Integer statusCode = feedResponse.getStatusCode();
            if(statusCode==201){
                System.debug('Sprint creado correctamente');
                System.debug(feedResponse.getBody());
            }
            else{
                System.debug('Error al crear Sprint');
            }
        }catch(Exception ex){
            System.debug('Error--'+ex.getMessage());
        }
        return null;
    }
    
    public static String sendSprintsByFieldValue(String idOpportunity, String idProject){
        List<Project__c> project = getProjectByOpportunityId(idOpportunity);
        Decimal sprintValue = project[0].Sprints__c; 
        for(Integer i=1; i<=sprintValue; i++){
            postSprints(idProject, i);
        }
        return null;
    }
    ///////////////////   BUILD JSON'S   ///////////////////////
    
    public static String JSONProject(String idOpportunity){
        List<Project__c> project = getProjectByOpportunityId(idOpportunity);
        String name = project[0].Name;
        String projectKey = project[0].Key__c;
        String leadAccountId = project[0].ScrumMaster__c;
        
        String payload = JSON.serialize(
            new map<String, Object>{
                'key' => projectKey,
                'name' => name,
                'projectTypeKey' => 'software',
                'projectTemplateKey' => 'com.pyxis.greenhopper.jira:gh-simplified-scrum-classic',
                'leadAccountId' => '61e6fa116b1ad90069afd60e', //61e6fa116b1ad90069afd60e
                'assigneeType' => 'PROJECT_LEAD',
                'avatarId' => '10200'
            }
        );
        return payload;
    }
  
    public static String JSONEpics(String oppId, String idproject){
        String jsonfinal;
        Quote quotes = [SELECT Id, Name FROM Quote WHERE OpportunityId =: oppId AND IsSyncing=true LIMIT 1];
    
        List<QuoteLineItem> qlitems = [SELECT Id, Product2.Name, Product2.Description FROM QuoteLineItem WHERE QuoteId =: quotes.Id];
 
        if(!qlitems.isEmpty()){
            JSONGenerator gen = JSON.createGenerator(true);
           
            gen.writeStartObject();     
            gen.writeFieldName('issueUpdates');
            gen.writeStartArray();
            for(Quotelineitem qli :qlitems){
                gen.writeStartObject();
                gen.writeFieldName('fields');
            
                    gen.writeStartObject();
                        gen.writeFieldName('project');
                        gen.writeStartObject();
                        gen.writeStringField('id', idproject);
                        gen.writeEndObject();
                        
                        gen.writeFieldName('issuetype');
                        gen.writeStartObject();
                        gen.writeStringField('id', '10000'); //10000 = epic issue
                        gen.writeEndObject();
                        
                        gen.writeStringField('customfield_10011', qli.Product2.Name);
                        gen.writeStringField('summary', qli.Product2.Description);
                    gen.writeEndObject();
                
                gen.writeEndObject();
            }
            gen.writeEndArray();
            gen.writeEndObject();
           
            String jsonData = gen.getAsString();
            jsonfinal = jsonData;
        }
        return jsonfinal;
    }
    
    public static String JSONHistory(String oppId, String idproject, List<String> lstkeyss){
        String payload;
        List<Character__c> character = [SELECT splitcharacter__c FROM Character__c ];
        String splitCharacter = character[0].splitcharacter__c; 
              
        List<Project__c> project = getProjectByOpportunityId(oppId);
        String scope = project[0].Scope__c;

        List<String> lstkey = lstkeyss;
        
        List<String> listkeys = new List<String>();        
        for(String key: lstkey){
            listkeys.add(key);
        }
        
        String prefinalstring = scope.replace('\r\n', '');
        List<String> prelstlines = prefinalstring.split(splitCharacter);
        
        if(!project.isEmpty()){
            JSONGenerator gen = JSON.createGenerator(true);
           
            gen.writeStartObject();     
            gen.writeFieldName('issueUpdates');
            gen.writeStartArray();
            
            for(String k : listkeys){
                for(String sc : prelstlines){ 
                    gen.writeStartObject();
                    gen.writeFieldName('fields');
                
                        gen.writeStartObject();
                            gen.writeFieldName('project');
                            gen.writeStartObject();
                            gen.writeStringField('id', idproject);
                            gen.writeEndObject();
                            
                            gen.writeFieldName('issuetype');
                            gen.writeStartObject();
                            gen.writeStringField('id', '10001');
                            gen.writeEndObject();
                            
                            gen.writeStringField('summary', sc);
                            gen.writeStringField('customfield_10014', k);
                        gen.writeEndObject();
                    
                    gen.writeEndObject();
                }
            }
            gen.writeEndArray();
            gen.writeEndObject();
           
            String jsonData = gen.getAsString();
            payload = jsonData;
        }
        return payload;
    }
    
    public static String JSONSprint(String oboardId, Integer i){
        String payload = JSON.serialize(
            new map<String, Object>{
                'name' => 'Sprint '+i,
                'originBoardId' => oboardId
            }
        );
        return payload;
    } 
}

Test Class

@isTest
public class JiraIntegrationControllerTest {
    
    static testMethod void methods(){
        ///////////   DATA    /////////////
        
        List<Opportunity> opportunity = [SELECT Name, Project__r.Name, 
                                         Project__r.Key__c, Project__r.Sprints__c 
                                         FROM Opportunity WHERE Name= 'Opp Name' LIMIT 1];
        Product2 product = new Product2(
            Name = 'Example Product',
            Description = 'This is the Product description.',
            ProductCode = 'EX1234',
            StockKeepingUnit = 'EX5678',
            Family = 'Example Product Family',
            QuantityUnitOfMeasure = 'inches',
            DisplayUrl = 'https://www.example.com/',
            ExternalId = 'ID #1234',
            IsActive = true);
        insert product;

        // Insert the Product in the Standard Price Book (if necessary)
        PricebookEntry standardPriceBookEntry = new PricebookEntry(
            Pricebook2Id = Test.getStandardPricebookId(),
            Product2Id = product.Id,
            UnitPrice = 100.00,
            UseStandardPrice = false,
            IsActive = true);
        insert standardPriceBookEntry;
        
        // Create a Pricebook
        Pricebook2 priceBook = new Pricebook2(
            Name = 'Small Business Price Book',
            Description = 'This is the Price Book description.',
            IsActive = true);
        insert priceBook;

        // Insert the Product in the New Price Book
        PricebookEntry priceBookEntry = new PricebookEntry(
            Pricebook2Id = priceBook.Id,
            Product2Id = product.Id,
            UnitPrice = 100.00,
            UseStandardPrice = false,
            IsActive = true
        );
        insert priceBookEntry;
        
        Account account = new Account(
            Name='Test Account',
            //Price_Book__c=priceBook.Id,
            NumberOfEmployees=20,
            Type='Customer',
            BillingStreet='av univ', 
            BillingCity='bj', 
            //BillingState='cdmx', 
            BillingCountry='Argentina', 
            BillingPostalCode='1704');
        insert account;
        
        Project__c project = new Project__c(
            Name = 'name',
            Scope__c = 'scope',
            Key__c = 'KEY');
        insert project;
        
        Opportunity oppTemplate = new Opportunity(
            Name='Opportunity Template',
            StageName='Discovery',
            CloseDate=System.today().addMonths(1),
            AccountId=account.Id,
            Pricebook2Id=priceBook.Id,
            Product_Families__c='Salesforce Marketing Cloud',
            RecordTypeId=Schema.SObjectType.Opportunity.getRecordTypeInfosByDeveloperName().get('Opportunity_Template').getRecordTypeId());
        insert oppTemplate;

        OpportunityLineItem cloneOpportunityLineItem = new OpportunityLineItem(
            OpportunityID=oppTemplate.Id,
            Quantity= 1,
            UnitPrice=1,
            Description='Prueba',
            Product2Id=product.Id,
            PricebookEntryId=pricebookEntry.Id);
        insert cloneOpportunityLineItem;  

        Opportunity opp = new Opportunity(
            Name='Opportunity',
            StageName='Discovery',
            CloseDate=System.today().addMonths(1),
            AccountId=account.Id,
            Pricebook2Id=priceBook.Id,
            Product_Families__c='Salesforce Marketing Cloud',
            JiraProjectKey__c = 'ASDF', 
            Project__c = project.Id);
        insert opp;

Quote quotes = new Quote (
            Name = 'Quote Test',
            OpportunityId = opp.id , 
            Clarification__c='clarification', 
            Pricebook2Id = priceBook.Id,
            Footer__c='Footer'); 
        insert quotes;
        
        QuoteLineItem qlitem = new QuoteLineItem(
            QuoteId = quotes.Id, 
            Quantity = 3.00, 
            Discount = 10.00, 
            UnitPrice = 12, 
            PricebookEntryId = priceBookEntry.id,
            Product2Id = product.Id);
        insert qlitem;
    
    System.debug('quote: ' + quotes);
    System.debug('qlitem: ' + qlitem);
        
        
        /////////     METHODS     ////////
        
        Test.startTest();
        
        //getBoardId - (WORKS!)  
        Test.setMock(HttpCalloutMock.class, new MockCallout());  
        String strRespGBI = JiraIntegrationController.getBoardId('10058');
        
        //postProject - (WORKS!) Covering postProject and JSONProject Methods
        Test.setMock(HttpCalloutMock.class, new MockCallout()); //
        Integer strRespPP = JiraIntegrationController.postProject(opp.Id);
        
        //sendSprintsByFieldValue - (WORKS!) 
        Test.setMock(HttpCalloutMock.class, new MockCallout());  
        String respSSBFV = JiraIntegrationController.sendSprintsByFieldValue(opp.Id, '10058');  
        
        //postSprints - (WORKS!) Covering postSprints and JSONSprint Methods
        Test.setMock(HttpCalloutMock.class, new MockCallout());  
        String respPS = JiraIntegrationController.postSprints('10058', 1);
        
        //postEpicIssues - No working,     
        Test.setMock(HttpCalloutMock.class, new MockCalloutPost());  
        String respPEI = JiraIntegrationController.postEpicsIssues(opp.Id, '10058');
        
        //postHistoryIssues
        
        Test.stopTest();
        
    }
    
}

MockCallout Class

@isTest
global class MockCallout implements HttpCalloutMock{
    global HTTPResponse respond(HTTPRequest req) {
        HttpResponse response = new HttpResponse();
        response.setHeader('Content-Type', 'application/json');
   
        response.setBody('{"animal": {"id":2, "name":"Test"}}');
        response.setStatusCode(200);
        return response;
    }
}

MockCalloutPost Class

@isTest
global class MockCalloutPost implements HttpCalloutMock{
    global HTTPResponse respond(HTTPRequest req) {
        HttpResponse response = new HttpResponse();
        response.setHeader('Content-Type', 'application/json');
        String expected = '{ '+
                          '     "issueUpdates" : [ '+
                          '          { '+
                          '             "fields": { '+
                          '                 "project": { '+
                          '                     "id": "10049" '+ 
                          '                 }, '+
                          '                 "issuetype": {  '+ 
                          '                     "id": "10000" '+
                          '                 },'+
                          '                 "customfield_10011": "EpicToGetBoard", '+
                          '                 "summary" : "DescripcionEjemplo" '+
                          '             } '+
                          '          } '+
                          '     ] '+
                          '}';
        response.setBody(expected);
        response.setStatusCode(201);
        return response;
    }
}

Debug quote, quotelineitem

DEBUG|quote: Quote:{Name=Quote Test, OpportunityId=0063K00000Ft8WUQAZ, Clarification__c=clarification, Pricebook2Id=01s3K0000003ReHQAU, Footer__c=Footer, Id=0Q03K0000006F5dSAE}

DEBUG|qlitem: QuoteLineItem:{QuoteId=0Q03K0000006F5dSAE, Quantity=3.00, Discount=10.00, UnitPrice=12, PricebookEntryId=01u3K000009A388QAC, Product2Id=01t3K000001zJORQA2, Id=0QL3K0000003sISWAY}
7
  • Please don't delete questions and re-post. From the previous question, a comment pointed out a potential issue that you never clarified or mentioned whether you debugged. In JSONEpics, you have a query expecting to return exactly one quote - yet you show no quote being created in your test class. Quote quotes = [SELECT Id, Name FROM Quote WHERE OpportunityId =: oppId AND IsSyncing=true LIMIT 1]; Commented Feb 28, 2022 at 14:57
  • I'm getting the same error, I already put Quote and QuoteLineItems. In the error refers to Class.JiraIntegrationController.JSONEpics: line 192, column 1. Commented Feb 28, 2022 at 15:24
  • The line is Quote quotes = [SELECT Id, Name FROM Quote WHERE OpportunityId =: oppId AND IsSyncing=true LIMIT 1]; In JiraControllerClass Commented Feb 28, 2022 at 15:25
  • Then it seems pretty straightforward. That query is not pulling any quotes and so will error as expected. You need to debug why it's not returning a quote in that query in your test environment as you own creating the quote so it exists against your test opp. You haven't shown where/if you create the quote in your test class, so I'm not sure how we could help you. Commented Feb 28, 2022 at 15:31
  • I made a debug and it is returning, I dont know what is missing Commented Feb 28, 2022 at 15:49

1 Answer 1

0

When debugging, it's always best to follow the trail (if you have one) and to try to walk through what your code is or should be doing.

In this example, you have an exact line called out for the error.

Quote quotes = [ SELECT 
                 Id, Name 
                 FROM Quote 
                 WHERE OpportunityId =: oppId AND IsSyncing=true 
                 LIMIT 1
               ];

Based on the error message, you know what's happening - no quotes are being returned. Even if you don't believe this to be true, an easy way to confirm it would be to add the following debug statement within your apex class before where it fails

List<Quote> checkQuotes = [ SELECT 
                            Id, Name 
                            FROM Quote 
                            WHERE OpportunityId =: oppId AND IsSyncing=true 
                            LIMIT 1
                          ];
System.debug(checkQuotes.size());

If nothing returns, you know the issue is in your test data setup. Looking at what test data you created, you see you created a Quote on the opportunity Id you're passing into the method

Quote quotes = new Quote (
            Name = 'Quote Test',
            OpportunityId = opp.id , 
            Clarification__c='clarification', 
            Pricebook2Id = priceBook.Id,
            Footer__c='Footer'); 
insert quotes;

So looking closer at the query, why would it return 0 records? You need to look at your criteria and what you're filtering on. You have two conditions

  1. Opportunity Id = oppId parameter in method
  2. IsSyncing = true

You know #1 is satisfied so it must follow that #2 is not true. This is why your query is returning 0 records in your test class. You need to set this field to true. You can't just directly set this field to true within your quote, you need to set the SyncedQuoteId field on the opportunity to sync these together.

The ID of the Quote that syncs with the opportunity. Setting this field lets you start and stop syncing between the opportunity and a quote. The ID has to be for a quote that is a child of the opportunity.

Quote quotes = new Quote (
            Name = 'Quote Test',
            OpportunityId = opp.id , 
            Clarification__c='clarification', 
            Pricebook2Id = priceBook.Id,
            Footer__c='Footer'); 
insert quotes;

//set quote to isyncing by linking it to the opportunity
opp.SyncedQuoteId = quotes.Id;
update opp;

This will allow your code to continue as it'll retrieve this quote in the query during your test.

1
  • Thanks a lot Kris!. My test class is done. Also I was getting another error similar to this. So I completed both of them. :) Commented Feb 28, 2022 at 18:25

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.