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
aam1raam1r 

Query returning no records in test after execution of Batch job

I'm hoping someone can help.  I have a batch job that is run via a button click in a Lightning page.  the fucnitonality works fine but i'm struggling with the test class.  After i call the Batch apex method to test, i'm getting full code coverage but any asserts afetr the run are not returning records as i'd expect.  In fact, it's not returning any records.

Here is the test code extract that is executing the batch class:
@isTest
    public static void test_FirstHeaderGeneratorBatch_2(){
        
        String OppToTest = 'Opportunity 1';

        Opportunity opp = [SELECT Id, First_Header_Required__c, First_Header_Created__c, AccountId FROM Opportunity WHERE Name =:OppToTest LIMIT 1];
        System.assert(opp.First_Header_Created__c==false, OppToTest+' should not have processed invoicing');    // succeeds

        Integer headersBefore = [SELECT Id FROM Header__c WHERE Opportunity__c = :opp.Id].size();
        System.assert(headersBefore == 0, OppToTest+' should not have generated headers');  // succeeds

        List<OpportunityLineItem> olis = [SELECT Id, Ordered_Date__c, First_Payment_Date__c, Supplier__r.Name
                                        FROM OpportunityLineItem WHERE OpportunityId =: opp.Id];
        for (OpportunityLineItem oli : olis) {
            System.assert(oli.Ordered_Date__c==null, OppToTest+' should not have ordered products');
        }

        for (OpportunityLineItem oli : olis) {
            if (oli.Supplier__r.Name=='InternalSupplier'){
                oli.Ordered_Date__c = System.today();  
                oli.First_Payment_Date__c = System.today().addMonths(1);
            }
        }

        update olis;

        Test.startTest(); ////////////////////////////////////////
            FirstHeaderGeneratorBatch.executeBatch(Date.today());
        Test.stopTest(); /////////////////////////////////////////
        
        Integer headersAfter = [SELECT Id FROM Header__c WHERE Opportunity__c = :opp.Id].size();
        
        /////// ASSERT FAILS
        System.assert(headersAfter > 0, 'Additional header(s) should have been created. Before: '+headersBefore+', After: '+headersAfter); 
    
    }
The batch does execute and teh logs are showing teh whole process, however the last assert is returning zero.  The query (at line 31) 
Integer headersAfter = [SELECT Id FROM Header__c WHERE Opportunity__c = :opp.Id].size();
.. returns zero records, although i can see the inserts succeeding as part of the batch class execution.

A debug on the batch class post insert:
insert headersToInsert;
 System.debug(' ^^^ LineItems inserted: '+[SELECT Id FROM Header__c].size());
..returns as expected in the logs:
User-added image
Can someone help me understand what i could be doing wrong please?

Many thanks for your time and apologies if my question was not asked in the best of ways.

regards,
Aamir
aam1raam1r
If i use the following to initiate the batch execution i get the same results
FirstInvoiceGeneratorBatch obj = new FirstInvoiceGeneratorBatch();
DataBase.executeBatch(obj);
Omar Rajab 94Omar Rajab 94
Hi Aamir, 

could you please share your batch class and the test class completly?

regards,
Omar
aam1raam1r

Hi Omar - and thanks for getting back to me.  Please find below an screened and slighytly trimmed version of the code, which contains all the essentials required.  I've only removed lengthy calculations and processes that will only clog-up the page.  Also i had to rename some of the fields and objects so as to remove any reference to the client.  Bearing that in mind this code is untested.

Batch Class:

global with sharing class FirstHeaderGeneratorBatch implements Database.Batchable<sObject>, Database.Stateful {

    @testvisible Boolean errorOccurred = false;
    @testVisible Exception processingException;

    Public Integer numberOfAccounts {get;set;}

    // Set Dates
    Date today;
    Date billToDate;
    Date headerDueDate;
    Public Static Integer daysInYear;
    Public Static Integer BILLING_DAY = 21;
    //Date invoicingDate;

    Date periodStart;   
    Date periodEnd;    

    global FirstHeaderGeneratorBatch () {
    
        periodStart = System.today();
        
        // Bill To Date
        today = Date.today();
        Integer months = (today.day() < 7) ? 1 : 2;
        billToDate = Date.parse( today.addMonths(months).toStartOfMonth().addDays(-1).format() );    

        // Header Date
        Datetime now = (Datetime)today;
        Integer daysToAdd = (now.format('E') == 'Sat') ? 13 : 12 - Integer.valueOf(now.format('u'));
        headerDueDate = Date.parse( Date.newInstance(today.year(), today.month(), today.day() + daysToAdd).format() ); // Date
        //headerDueDate = Date.newInstance(today.year(), today.month(), today.day() + daysToAdd).format();         // String   

        periodEnd = billToDate; // invoicingDate.addMonths(2).toStartOfMonth().addDays(-1);
    }

    global List<Opportunity> start(Database.BatchableContext context) {
        
        return [ SELECT Id, Name, Billing_Restart_Date__c, Header_Generated_Id__c, AccountId, Account.ParentId,
                    (SELECT Opportunity_Name__c, Product_Name__c, Supplier__r.Name, Status__c, Cost_Price__c,
                        ServiceDate, Ordered_Date__c, Order_Accepted_Date__c, Billing_Restart_Date__c, Opp_Close_Date__c,
                        Service_Start_Date__c, Service_Stop_Date__c, Estimated_Delivery_Date__c, Estimated_First_Collection__c,
                        Delivered_Date__c, First_Payment_Date__c, Last_Collection_Date__c, Last_Header_Start__c, Last_Header_End__c,
                        Collection_Frequency__c, UnitPrice, Quantity, Billing_Frequency__c, Header_Quantity__c, Paid_In_Full__c, 
                        Supplier__c, CreatedById, Product2Id, Product2.Name, Tax_Percent__c, 
                        Collections_per_Year__c,    // For calculating charges for products billed less than Monthly (more than? depends how you look at it!)
                        Opportunity.AccountId,      
                        Notes__c       
                    FROM OpportunityLineItems
                    WHERE Collection_Frequency__c != 'Call Off'
                    AND Paid_In_Full__c = false
                    AND Last_Header_Start__c = null
                    AND Last_Header_End__c = null
                    AND Status__c NOT IN ('Inactive','Rejected by Supplier'))
                FROM Opportunity WHERE First_Header_Required__c = true AND First_Header_Created__c = false AND CloseDate <= :Date.Today()]; //
    }
  
    global void execute(Database.BatchableContext context, List<Opportunity> lstOpps){

        List<OpportunityLineItem> lstLineItems  = new List<OpportunityLineItem>();
        Map<Opportunity, List<OpportunityLineItem>>  mapOppsLineItems = new Map<Opportunity, List<OpportunityLineItem>>();

        if (!errorOccurred) {

            Savepoint sp = Database.setSavepoint();

            try {
                // PROCESS QUERY RESULTS TO RETAIN REQUIRED PRODUCTS
                // Build Map for query results
                for (Opportunity o : lstOpps) {
                    for (OpportunityLineItem oli : o.OpportunityLineItems){
                        if (mapOppsLineItems.containsKey(o)) {
                            mapOppsLineItems.get(o).add(oli);    // add product to the same Opp
                        } else {
                            mapOppsLineItems.put(o, new OpportunityLineItem[] { oli } );  // add the first Opp and product
                        }
                    }                
                }

                // CREATE INVOICE: Header__c RECORDS FROM INCLUSIVE PRODUCTS MAP
                Id headerRecordTypeId = Schema.SObjectType.Header__c.getRecordTypeInfosByDeveloperName().get('Header').getRecordTypeId();

                // For every opportunity CREATE header ///////////////////////////////////////////
                //////////////////////////////////////////////////////////////////////////////////////////
                List<Header__c> headersToCreate = new List<Header__c>{};
                List<Opportunity> lstOppsToUpdate = new List<Opportunity>();

                for (Opportunity opp : mapOppsLineItems.keySet()){

                    // Get earliest start date
                    for (OpportunityLineItem oli : mapOppsLineItems.get(opp)) {
                        checkDate = (oli.Supplier__r.Name=='InternalSupplier') ? oli.Service_Start_Date__c : oli.First_Payment_Date__c; 
                        if ( earliestStartDate == null || earliestStartDate > checkDate) {
                            earliestStartDate = checkDate;
                        } 
                    }

                    headersToCreate.add ( new Header__c (
                        Account__c = opp.AccountId,
                        Opportunity__c = opp.Id,
                        RecordTypeId = headerRecordTypeId,
                    ));

                    earliestStartDate = null;
                    System.debug('Creating Header Header. Opp: '+opp.Name);
                } 

                insert headersToCreate;    // GENERATE Header PER OPPORTUNITY
                //////////////////////////////////////////////////////////////////////////////////////////
                //////////////////////////////////////////////////////////////////////////////////////////


                // CREATE HEADER LINE ITEMS
                ID headerId;
                Header_Line_Item__c            headerLineItem;    // individual header libne item record
                List<Header_Line_Item__c>      newHeaderLineItems = new List<Header_Line_Item__c> {};    // temp list per opp/header
                List<Header_Line_Item__c>      headerLineItemsToInsert = new List<Header_Line_Item__c> {};  // final list to insert
                List<OpportunityLineItem>      lstOLIsToUpdate = new List<OpportunityLineItem>();
                List<Header__c>  headersToDelete = new List<Header__c>{};    // list built up once line items processed 
                
                for (Opportunity opp : mapOppsLineItems.keySet()){
                    // Get Header ID
                    for (Header__c fi : headersToCreate){
                        if (opp.Id == fi.Opportunity__c) {
                            headerId = fi.Id;
                            earliestStartDate = fi.Billing_Period_Start__c;
                        }
                    }

                    for (OpportunityLineItem oli : mapOppsLineItems.get(opp)) {
                        
                        Date lineItemStart = earliestStartDate; // periodStart;   
                        Date lineItemEnd = periodEnd;

                        if (oli.First_Payment_Date__c != null) {
                            if ( oli.First_Payment_Date__c <= periodEnd ) {
                                lineItemStart = oli.First_Payment_Date__c;
                            } else if (oli.Collection_Frequency__c.startsWithIgnoreCase('Once Every') && oli.Delivered_Date__c <= periodEnd) {
                                lineItemStart = oli.Delivered_Date__c;
                            } else {
                                lineItemStart = oli.Service_Start_Date__c;
                            }
                        }
                        

                        if ( oli.Service_Stop_Date__c != null && lineItemStart <= oli.Service_Stop_Date__c && oli.Service_Stop_Date__c <= periodEnd) {
                            lineItemEnd = oli.Service_Stop_Date__c;
                        }
                        else if ( oli.Last_Collection_Date__c != null && lineItemStart <= oli.Last_Collection_Date__c && oli.Last_Collection_Date__c <= periodEnd) {
                            lineItemEnd = oli.Last_Collection_Date__c;
                        }  // */

                        invoiceFrequency = (lineItemStart.monthsBetween(lineItemEnd)) > 0 ? (monthsBetween + 1) * oliQuantity : oliQuantity;

                        // Create header line with frequency directly
                        headerLineItem = createHeaderLineItem(headerId, oli, headerFrequency, lineItemStart, lineItemEnd);

                        oli.Last_Header_Start__c = lineItemStart;
                        oli.Last_Header_End__c = lineItemEnd;
                        
                        
                        if (headerLineItem != null) {
                            newHeaderLineItems.add(headerLineItem);
                            headerLineItem = null;
                        }
                        lstOLIsToUpdate.add(oli);
                        ///  END MONTHLY ////////////////////////////////////////////
                    }   // END: for every opp line item    


                    // Add Inv Line Items to insert list and update Opp with Header Created and Billing Day
                    if (newHeaderLineItems.size() > 0) {

                        cwProducts = 0;  // Count CW Products per opportunity/header
                        for (Header_Line_Item__c ili : newHeaderLineItems) {
                            if (ili.Supplier__r.Name=='InternalSupplier') { cwProducts++; }
                            System.debug('Inserting Header Line Items '+ili.Name);
                        }

                        // Exclude Headers with only CW Products
                        if (cwProducts == newHeaderLineItems.size()) {
                            // Revert oli dates - match upon invLineItem.Product_Line_Item_ID__c = oli.Id
                            for (OpportunityLineItem oli : lstOLIsToUpdate) {
                                if (oli.Id == newHeaderLineItems[0].Product_Line_Item_ID__c) {
                                    oli.Last_Header_Start__c = null;
                                    oli.Last_Header_End__c = null;
                                }
                            }
                        } else {
                            headerLineItemsToInsert.addAll(newHeaderLineItems);
                            newHeaderLineItems.clear();

                            opp.First_Header_Created__c = true;
                            opp.Billing_Day__c = BILLING_DAY;
                            lstOppsToUpdate.add(opp);
                        }

                    } else {
                        headersToDelete.add( new Header__c(Id=headerId));  // REMOVE Empty Headers
                    }
                    
                }   // END: for every Opp                

                ////////////////////////////////////////////////////////////////////////////////
                // DML: INSERT INVOICE LINES
                insert headerLineItemsToInsert;

                // DML: UPDATE OPP AND OPP PRODUCTS
                update lstOppsToUpdate;
                update lstOLIsToUpdate;
                
                // DML: DELETE INVOICES WITH NO LINES
                if (headersToDelete.size() > 0) {
                    for (Header__c fid : headersToDelete) {
                        Integer i = 0;
                        while (i < headersToCreate.size()) {
                            System.debug(' *** i='+i);
                            if (headersToCreate.get(i).Id == fid.Id) {
                                headersToCreate.remove(i); // increments to next header
                            } else {
                                i++;
                            }
                            //i++;  // this would shift the index so must remain within else !!
                        }
                    }
                    
                    delete headersToDelete;
                }

                // DML: UPDATE INVOICE 
                for (Header__c fic : headersToCreate){
                    fic.Status__c = 'Pending Header';
                }

                update headersToCreate;
                ////////////////////////////////////////////////////////////////////////////////
            }

            catch (Exception ex) {
                // If an exception occurs during processing, wind back the changes that have been made,
                // and mark the process as failed so that no further batches are processed.
                Database.rollback(sp);
                processingException = ex;
                errorOccurred = true;
            }
        }
    }
  
    global void finish(Database.BatchableContext context){

        if (errorOccurred) {
            // If an error occcurred, send an error email to the development team.
            sendErrorEmail();
        }
    }

    @testVisible
    private Messaging.SingleEmailMessage sendErrorEmail() {
        
        String plainTextBody = 'Batch Run Time: ' + Datetime.now();

        return EmailUtilities.sendErrorEmailMessage('Error Occurred Generating Headers', plainTextBody, processingException);
    }
    
    private Header_Line_Item__c createHeaderLineItem(Id headerId, OpportunityLineItem oli, Decimal collectionsToCharge, Date startDate, Date stopDate) {
        
        Decimal unitPrice = oli.UnitPrice.setscale(3,System.RoundingMode.HALF_UP);
        Decimal netPrice = oli.UnitPrice * collectionsToCharge; 
        Decimal totalTax = netPrice * (oli.Tax_Percent__c / 100);
        Decimal grossPrice = netPrice + totalTax;

        Header_Line_Item__c lineItem = new Header_Line_Item__c(
            Header__c = headerId,
            Quantity__c = collectionsToCharge,
            Name = oli.Product2.Name,
            Unit_Price__c = oli.UnitPrice,
            Net_Amount__c = netPrice,
            Gross_Amount__c = grossPrice,
            Product__c = oli.Product2Id,
            Tax__c = totalTax,
            Product_Line_Item_ID__c = oli.Id,
            Supplier__c = oli.Supplier__c,
            Commission_Recipient__c = oli.CreatedById,
            Billing_Period_Start__c = startDate,
            Billing_Period_End__c = stopDate
        );

        return lineItem;
    }



    // A utility method to execute the batch job, presets the batch size to the correct batch size
    global static void executeBatch(Date invoicingDate) {
        //FirstHeaderGeneratorBatch headerBatch = new FirstHeaderGeneratorBatch(invoicingDate);
        FirstHeaderGeneratorBatch headerBatch = new FirstHeaderGeneratorBatch();
        Database.executeBatch(headerBatch, 200);
    }

}

 
aam1raam1r
I Had tro trim the Test class even more to fit in to this answer:
@isTest (SeeAllData=false)
public class HeaderGeneratorController_Test {
    
    public static Integer validLineItems = 0;

    @TestSetup  
    
    static void SetupData() {
        setupUsers();
        generateData();
    } // END SetupData
        
    @future
    static void generateData(){
        
        // Get Supplier RecordTypeId for Account
        Map <String,Schema.RecordTypeInfo> accountRecordTypeMap = Schema.SObjectType.Account.getRecordTypeInfosByName();
        Id supplierRecordTypeId = accountRecordTypeMap.get('Supplier').getRecordTypeId();
        Id customerRecordTypeId = accountRecordTypeMap.get('Customer').getRecordTypeId();
        
        // Get InternalSupplier RecordTypeId for Opportunity
        Map <String,Schema.RecordTypeInfo> opportunityRecordTypeMap = Schema.SObjectType.Opportunity.getRecordTypeInfosByName();
        Id cheaperWasteRecordTypeId = opportunityRecordTypeMap.get('InternalSupplier').getRecordTypeId();

        // Dates for Opportunties and Porducts
        Date pastDate = Date.today().addDays(-7);
        Date presentDate = Date.today();
        Date futureDate = Date.today().addDays(1);
        
        // Create Supplier Accounts
        List<Account> supplierAccounts = new List<Account> {
            new Account (Name = 'Supplier 1' , BillingStreet = 'Supplier 1 Street', BillingCity = 'Supplier 1 City', 
                         BillingPostalCode = 'AA1 1AA', ShippingStreet = 'Supplier 1 Street', ShippingCity = 'Supplier 1 City', 
                         ShippingPostalCode = 'AA1 1AA', RecordTypeId = supplierRecordTypeId, WTN_Waste_Carrier_Licence_Number__c = 'SP1000000',
                         Active_Supplier__c = true, Uses_Supplier_Pricing__c = true
                        )
        };
        insert supplierAccounts;
            
        // Create Customer Accounts
        List<Account> customerAccounts = new List<Account> {
            new Account (Name = 'Customer 1' , BillingStreet = 'Customer 1 Street', BillingCity = 'Customer 1 City', 
                         BillingPostalCode = 'CC1 1CC', ShippingStreet = 'Customer 1 Street', ShippingCity = 'Customer 1 City', 
                         ShippingPostalCode = 'CC1 1CC', RecordTypeId = customerRecordTypeId, InternalSupplier_BP_Number__c = 'BP111111',
                         Access_Restrictions__c = 'Other (Please Detail)', Restriction_Details__c = 'Collect from rear. Code for gate is C1111.',
                         comp_house__Company_Type__c = 'LTD', SIC = '111111', comp_house__Company_Number__c = '11111111'
                        )
        };
        insert customerAccounts;
         
        // Create Contacts
        List<Contact> contacts = new List<Contact> {
            new Contact (FirstName = 'FirstName1', LastName = 'LastName1', Email = 'contact1Email@test.com', 
                         BirthDate = Date.today().addYears(-20), Phone = '01111111111', AccountId = customerAccounts[0].Id
                        )
        };
        insert contacts;
        
        // Create Opportunities
        List<Opportunity> opportunities = new List<Opportunity> {
            new Opportunity (Name = 'Opportunity 1', StageName = 'Waste Sale', CloseDate = Date.today().addDays(-1),
                             Conga_Signer__c = contacts[0].Id, AccountId = customerAccounts[0].Id, RecordTypeId = cheaperWasteRecordTypeId,
                             Billing_Restart_Date__c = futureDate,
                             First_Header_Required__c = true, First_Header_Created__c = false
                            ),
            new Opportunity (Name = 'Opportunity 2', StageName = 'Waste Sale', CloseDate = Date.today().addDays(-1),
                             Conga_Signer__c = contacts[1].Id, AccountId = customerAccounts[0].Id, RecordTypeId = cheaperWasteRecordTypeId,
                             Billing_Restart_Date__c = presentDate,
                             First_Header_Required__c = true, First_Header_Created__c = false
                            ),
            new Opportunity (Name = 'Opportunity 3', StageName = 'Waste Sale', CloseDate = Date.today().addDays(-1),
                             Conga_Signer__c = contacts[2].Id, AccountId = customerAccounts[0].Id, RecordTypeId = cheaperWasteRecordTypeId,
                             Billing_Restart_Date__c = pastDate,
                             First_Header_Required__c = true, First_Header_Created__c = false
                            )
        };
        insert opportunities;

        // Create Products
        List<Product2> productsToInsert = new List<Product2>();

        Product2    prd_1100LB_GW = new Product2(Name='1100 Litre Bin (General Waste)',IsActive=TRUE,Product_Type__c='Lift');
        productsToInsert.add(prd_1100LB_GW); 
        Product2    prd_1100LB_MR = new Product2(Name='1100 Litre Bin (Mixed Recycling)',IsActive=TRUE,Product_Type__c='Lift');
        productsToInsert.add(prd_1100LB_MR); 
        Product2    prd_11LB_Sh   = new Product2(Name='11 Litre Bin (Sharps)',IsActive=TRUE,Product_Type__c='Lift');
        productsToInsert.add(prd_11LB_Sh); 
        Product2    prd_240LB_Gl  = new Product2(Name='240 Litre Bin (Glass)',IsActive=TRUE,Product_Type__c='Lift');
        productsToInsert.add(prd_240LB_Gl); 

        Product2    prd_DOC       = new Product2(Name='Duty of Care',IsActive=TRUE,Product_Type__c='DOC');
        productsToInsert.add(prd_DOC);    // insert      prd_DOC;      // Collection_Frequency__c = ''

        Product2    prd_BinPrt    = new Product2(Name='Bin Protection',IsActive=TRUE,Product_Type__c='Insurance');
        productsToInsert.add(prd_BinPrt);    // insert      prd_BinPrt;      //

        Database.SaveResult[] resultProds = Database.insert(productsToInsert);
        
        // Check results.
        for (Integer i = 0; i < resultProds.size(); i++) {
            if (resultProds[i].isSuccess()) {
                System.debug('Successfully created ID: ' + resultProds[i].getId());
            } 
            else {
                System.debug('Error: could not create sobject ' + 'for array element ' + i + '.');
                System.debug('The error reported was: ' + resultProds[i].getErrors()[0].getMessage() + '\n');
            }
        }

        Double amt_1100LB_GW = 11.50, amt_1100LB_MR = 08.65, amt_11LB_Sh = 03.00, amt_240LB_Gl = 11.50, amt_DOC = 95.00, amt_BinPrt = 24.00 ;

        PriceBookEntry  pbe_1100LB_GW = new PriceBookEntry(UnitPrice = amt_1100LB_GW, PriceBook2Id = Test.getStandardPricebookId(),Product2Id = prd_1100LB_GW.Id, IsActive = true);
            insert      pbe_1100LB_GW;       //
            Id gw1100 = pbe_1100LB_GW.Id;
        PriceBookEntry  pbe_1100LB_MR = new PriceBookEntry(UnitPrice = amt_1100LB_MR, PriceBook2Id = Test.getStandardPricebookId(),Product2Id = prd_1100LB_MR.Id, IsActive = true);
            insert      pbe_1100LB_MR;       //
            Id mr1100 = pbe_1100LB_MR.Id;
        PriceBookEntry  pbe_11LB_Sh   = new PriceBookEntry(UnitPrice = amt_11LB_Sh,   PriceBook2Id = Test.getStandardPricebookId(),Product2Id = prd_11LB_Sh.Id,   IsActive = true);
            insert      pbe_11LB_Sh;       //
            Id sharps11=pbe_11LB_Sh.Id;
        PriceBookEntry  pbe_240LB_Gl =  new PriceBookEntry(UnitPrice = amt_240LB_Gl, PriceBook2Id = Test.getStandardPricebookId(), Product2Id = prd_240LB_Gl.Id, IsActive = true);
            insert      pbe_240LB_Gl;       //
            Id glass240=pbe_240LB_Gl.Id;
        PriceBookEntry  pbe_DOC =       new PriceBookEntry(UnitPrice = amt_DOC, PriceBook2Id =      Test.getStandardPricebookId(), Product2Id = prd_DOC.Id,     IsActive = true);
            insert      pbe_DOC;       //
            Id doc =    pbe_DOC.Id;
        PriceBookEntry  pbe_BinPrt =    new PriceBookEntry(UnitPrice = amt_BinPrt, PriceBook2Id =   Test.getStandardPricebookId(), Product2Id = prd_BinPrt.Id,  IsActive = true);
            insert      pbe_BinPrt;       //
            Id ins =    pbe_BinPrt.Id;
        
        Account cwSupp = new Account(Name='InternalSupplier',RecordTypeId=supplierRecordTypeId);
        insert cwSupp;
        
        //Date oliDate = Date.today().addMonths(-1);
        
        // Opp1 - Future Collectionas and delivery: validLineItems to add += 0
        validLineItems += 0;
        List<OpportunityLineItem> oli1 = New List<OpportunityLineItem> {
            New OpportunityLineItem (OpportunityId = opportunities[0].Id, Actual_Cost_Price__c = 12.20, Status__c = 'Active', Cost_Price__c = 1,   //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = gw1100,
                                    Quantity = 1, UnitPrice = 20, Supplier__c = supplierAccounts[0].Id, 
                                    Ordered_Date__c = futureDate,  
                                    Order_Accepted_Date__c = futureDate, 
                                    Estimated_Delivery_Date__c = futureDate, 
                                    Delivered_Date__c = futureDate, 
                                    First_Collection_Date__c = futureDate, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[0].Id, Actual_Cost_Price__c = 7.70, Status__c = 'Active', Cost_Price__c = 2,    //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = mr1100,
                                    Quantity = 1, UnitPrice = 15, Supplier__c = supplierAccounts[1].Id, 
                                    Ordered_Date__c = futureDate,  
                                    Order_Accepted_Date__c = futureDate, 
                                    Estimated_Delivery_Date__c = futureDate, 
                                    Delivered_Date__c = futureDate, 
                                    First_Collection_Date__c = futureDate, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),

            //DOC    
            New OpportunityLineItem (OpportunityId = opportunities[0].Id, Actual_Cost_Price__c = 0, Status__c = 'Active', Billing_Frequency__c = 'Annually',    //////////
                                     Collection_Frequency__c = 'N/A', Cost_Price__c = 3, PricebookEntryId = doc, Quantity = 1, UnitPrice = 96, Supplier__c = cwSupp.Id,
                                     Service_Start_Date__c = null, Service_Stop_Date__c = null, Last_Header_End__c = null
                					),
            //INS
            New OpportunityLineItem (OpportunityId = opportunities[0].Id, Actual_Cost_Price__c = 0, Status__c = 'Active', Billing_Frequency__c = 'Annually',    //////////
                                     Collection_Frequency__c = 'N/A', Cost_Price__c = 4, PricebookEntryId = ins, Quantity = 1, UnitPrice = 26, Supplier__c = cwSupp.Id,
                                     Service_Start_Date__c = null, Service_Stop_Date__c = null, Last_Header_End__c = null
                					)
        };
        insert oli1;
        
        // Opp2 - Only 3 of 5 Lift products have First Collection Date
        validLineItems += 5;
        List<OpportunityLineItem> oli2 = New List<OpportunityLineItem> {
            New OpportunityLineItem (OpportunityId = opportunities[1].Id, Actual_Cost_Price__c = 12.20, Status__c = 'Active', Cost_Price__c = 5,    //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = gw1100,
                                    Quantity = 1, UnitPrice = 20, Supplier__c = supplierAccounts[0].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = null, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[1].Id, Actual_Cost_Price__c = 7.70, Status__c = 'Active', Cost_Price__c = 6,    //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = mr1100,
                                    Quantity = 1, UnitPrice = 15, Supplier__c = supplierAccounts[1].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = null, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[1].Id, Actual_Cost_Price__c = 4.00, Status__c = 'Active', Cost_Price__c = 7, 
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = glass240,
                                    Quantity = 1, UnitPrice = 9.98, Supplier__c = supplierAccounts[2].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = futureDate, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[1].Id, Actual_Cost_Price__c = 6.00, Status__c = 'Active', Cost_Price__c = 8, 
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = sharps11,
                                    Quantity = 1, UnitPrice = 23.00, Supplier__c = supplierAccounts[3].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = futureDate, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[1].Id, Actual_Cost_Price__c = 6.00, Status__c = 'Active', Cost_Price__c = 9, 
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = sharps11,
                                    Quantity = 1, UnitPrice = 23.00, Supplier__c = supplierAccounts[3].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = futureDate, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),



            //DOC    
            New OpportunityLineItem (OpportunityId = opportunities[1].Id, Actual_Cost_Price__c = 0, Status__c = 'Active', Billing_Frequency__c = 'Monthly', 
                                     Collection_Frequency__c = 'N/A', Cost_Price__c = 10, PricebookEntryId = doc, Quantity = 1, UnitPrice = 56, Supplier__c = cwSupp.Id,
                                     Service_Start_Date__c = null, Service_Stop_Date__c = null, Last_Header_End__c = null
                					),
            //INS
            New OpportunityLineItem (OpportunityId = opportunities[1].Id, Actual_Cost_Price__c = 0, Status__c = 'Active', Billing_Frequency__c = 'Monthly', 
                                     Collection_Frequency__c = 'N/A', Cost_Price__c = 11, PricebookEntryId = ins, Quantity = 1, UnitPrice = 26, Supplier__c = cwSupp.Id,
                                     Service_Start_Date__c = null, Service_Stop_Date__c = null, Last_Header_End__c = null
                					)
        };
        insert oli2;
        
        // Opp3 - All removed as no First Collection Date on any Lift products OR Delivered Date is null or future dated
        validLineItems += 0;
        List<OpportunityLineItem> oli3 = New List<OpportunityLineItem> {
            New OpportunityLineItem (OpportunityId = opportunities[2].Id, Actual_Cost_Price__c = 12.20, Status__c = 'Active', Cost_Price__c = 12,    //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = gw1100,
                                    Quantity = 1, UnitPrice = 20, Supplier__c = supplierAccounts[0].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = futureDate, 
                                    First_Collection_Date__c = futureDate, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[2].Id, Actual_Cost_Price__c = 7.70, Status__c = 'Active', Cost_Price__c = 13,    //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = mr1100,
                                    Quantity = 1, UnitPrice = 15, Supplier__c = supplierAccounts[1].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = null, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[2].Id, Actual_Cost_Price__c = 4.00, Status__c = 'Active', Cost_Price__c = 14,    //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = glass240,
                                    Quantity = 1, UnitPrice = 9.98, Supplier__c = supplierAccounts[2].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = null, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[2].Id, Actual_Cost_Price__c = 6.00, Status__c = 'Active', Cost_Price__c = 15,    //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = sharps11,
                                    Quantity = 1, UnitPrice = 23.00, Supplier__c = supplierAccounts[3].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = null, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),
            New OpportunityLineItem (OpportunityId = opportunities[2].Id, Actual_Cost_Price__c = 6.00, Status__c = 'Active', Cost_Price__c = 16,    //////////
                                    Collection_Days__c = 'Monday', Billing_Frequency__c = 'Monthly', Collection_Frequency__c = 'Weekly', PricebookEntryId = sharps11,
                                    Quantity = 1, UnitPrice = 23.00, Supplier__c = supplierAccounts[3].Id, 
                                    Ordered_Date__c = pastDate,  
                                    Order_Accepted_Date__c = pastDate, 
                                    Estimated_Delivery_Date__c = pastDate, 
                                    Delivered_Date__c = pastDate, 
                                    First_Collection_Date__c = null, 
                                    Last_Collection_Date__c = null, 
                                    Last_Header_End__c = null
            ),



            //DOC    
            New OpportunityLineItem (OpportunityId = opportunities[2].Id, Actual_Cost_Price__c = 0, Status__c = 'Active', Billing_Frequency__c = 'Monthly',    //////////
                                     Collection_Frequency__c = 'N/A', Cost_Price__c = 17, PricebookEntryId = doc, Quantity = 1, UnitPrice = 0, Supplier__c = cwSupp.id,
                                     Service_Start_Date__c = null, Service_Stop_Date__c = null, Last_Header_End__c = null
                					),
            //INS
            New OpportunityLineItem (OpportunityId = opportunities[2].Id, Actual_Cost_Price__c = 0, Status__c = 'Active', Billing_Frequency__c = 'Monthly',    //////////
                                     Collection_Frequency__c = 'N/A', Cost_Price__c = 18, PricebookEntryId = ins, Quantity = 1, UnitPrice = 26, Supplier__c = cwSupp.Id,
                                     Service_Start_Date__c = null, Service_Stop_Date__c = null, Last_Header_End__c = null
                					)
        };
        insert oli3;
                    
    }   // END generateData
    
    // //////////////////////////////////////////////////////////////////////////////////////////////////
    @isTest // GENERATE INITIAL LIST OF PRODUCTS
    public static void test_getInitProductList(){
        // Generate List
        HeaderGeneratorController.firstHeaderWrapper lineItems;
        lineItems = HeaderGeneratorController.getInitProductList();   // TEST METHOD

        List<OpportunityLineItem> oliList = [SELECT Id FROM OpportunityLineItem];
        Integer lineItemsAdded = oliList.size();

        validLineItems = 16;    // lines included. Updates to this from generateData() method don't hold value
        
        Integer countLineItems = lineItems.lstLineItems.size(); // Test Count of line items
        
        // * Debugging items Listed and removed - Note helps see reason for removal, Serial# helps identify which item was removed/included in debug logs
        for (OpportunityLineItem oli : lineItems.lstLineItems) {
            System.debug( ' Product Listed: '+oli.Product_Name__c+' Note: '+oli.Notes__c+' First Collection '+oli.First_Collection_Date__c+' Billing Restart '+oli.Billing_Restart_Date__c+' Serial# '+oli.Cost_Price__c);
        }  
        for (OpportunityLineItem oli : lineItems.lstLineItemsRemoved) {
            System.debug( ' Product Removed: '+oli.Product_Name__c+' Note: '+oli.Notes__c+' First Collection '+oli.First_Collection_Date__c+' Billing Restart '+oli.Billing_Restart_Date__c+' Serial# '+oli.Cost_Price__c);
        }  // */

        // Assert count of line items included and excluded from first header
        System.assert( countLineItems == validLineItems, countLineItems+' products returned, but '+validLineItems+' expected!' );
        System.assert( lineItemsAdded - validLineItems == lineItems.lstLineItemsRemoved.size(), lineItemsAdded+' Line Items added. '+validLineItems+' valid lines. '+lineItems.lstLineItemsRemoved.size()+' removed.' );

    }

    @isTest
    public static void test_FirstHeaderGeneratorBatch_2(){
        String OppToTest = 'Opportunity 1';

        Opportunity opp = [SELECT Id, First_Header_Required__c, First_Header_Created__c, AccountId FROM Opportunity WHERE Name =:OppToTest LIMIT 1];
        System.assert(opp.First_Header_Created__c==false, OppToTest+' should not have processed invoicing');    // succeeds

        Integer headersBefore = [SELECT Id FROM Header__c WHERE Opportunity__c = :opp.Id].size();
        System.assert(headersBefore == 0, OppToTest+' should not have generated headers');  // succeeds

        List<OpportunityLineItem> olis = [SELECT Id, Ordered_Date__c, First_Payment_Date__c, Supplier__r.Name
                                        FROM OpportunityLineItem WHERE OpportunityId =: opp.Id];
        for (OpportunityLineItem oli : olis) {
            System.assert(oli.Ordered_Date__c==null, OppToTest+' should not have ordered products');
        }

        for (OpportunityLineItem oli : olis) {
            if (oli.Supplier__r.Name=='InternalSupplier'){
                oli.Ordered_Date__c = System.today();  
                oli.First_Payment_Date__c = System.today().addMonths(1);
            }
        }

        update olis;

        Test.startTest(); ////////////////////////////////////////
            FirstHeaderGeneratorBatch.executeBatch(Date.today());
        Test.stopTest(); /////////////////////////////////////////
        
        Integer headersAfter = [SELECT Id FROM Header__c WHERE Opportunity__c = :opp.Id].size();
        
        /////// ASSERT FAILS
        System.assert(headersAfter > 0, 'Additional header(s) should have been created. Before: '+headersBefore+', After: '+headersAfter);
    }
  
}