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
Amol Gaikwad 13Amol Gaikwad 13 

Need Help to cover batch apex test class current coverage is 65%

Apex Class-

global class groupMasterBatch implements Database.Batchable<sObject>, Database.Stateful {
    global Set<ID> sourceIDs        { get; set; }
    global ID   destID              { get; set; }
    global string opType            { get; set; }
    global boolean failed           { get; set; }
    global boolean hasGroups        { get; set; }
    global boolean deleteSources    { get; set; }

    global Database.QueryLocator start(Database.BatchableContext BC){    
        failed = false;
        
        string q;
        ID[] roles = new ID[] {};
        ID[] profiles = new ID[] {};
        ID[] groups = new ID[] {};
        
        string rolePref = Schema.Sobjecttype.UserRole.getKeyPrefix();
        string proPref = Schema.Sobjecttype.Profile.getKeyPrefix();
        system.debug('rolePref'+rolePref);
        system.debug('proPref'+proPref);
        system.debug('sourceIDs'+sourceIDs);
        for( ID source : sourceIDs )
            if( (''+ source).substring( 0, 3 ) == proPref )
                profiles.add( source );
            else if( ('' + source).substring( 0, 3 ) == rolePref )
                roles.add( source );
            else
                groups.add( source );
                
        hasGroups = !groups.isEmpty();
        
        if( opType == groupMaster.MEMBER_OP ) {
            if( hasGroups )
                q = 'select memberid '+
                    'from collaborationgroupmember '+
                    'where collaborationgroupid IN :groups';
            else
                q = 'select id '+
                    'from user '+
                    'where isactive = true and ( usertype=\'Standard\' or usertype = \'CSNOnly\' ) '+
                    'and ( '+ 
                    ( !roles.isEmpty() ? 'userroleid IN :roles ' : 'id = null ' ) +
                    ' OR '+
                    ( !profiles.isEmpty() ? 'profileid in :profiles ' : 'id = null ' ) +' ) ';
        } else if( opType == groupMaster.COPY_OP || opType == groupMaster.MERGE_OP )
            q = 'select id, createdbyid, createddate, body, type, relatedrecordid, contentfilename, '+
                'LinkUrl, Title, '+
                '(select createdbyid from FeedLikes), '+
                '(select feeditemid, commentbody, createdbyid, createddate from FeedComments) '+
                'from CollaborationGroupFeed '+
                'where parentid IN :groups and type <> \'TrackedChange\' '+
                'order by id asc';  
                        
        return Database.getQueryLocator(q);
    }

    global void execute(Database.BatchableContext BC, List<sObject> scope){
        ID[] userIDs = new ID[] {};
        
        if( scope == null || scope.isEmpty() )
            return;
        
        // If we're copying members, extract the userids to add
        if( opType == groupMaster.MEMBER_OP ) {
            CollaborationGroupMember[] toAdd = new CollaborationGroupMember[] {};
            
            for( sObject g : scope )
                userIDs.add( ID.valueof( string.valueof( g.get( ( hasGroups ? 'memberid' : 'id' ) ) ) ) );

            if( userIDs.isEmpty() )
                return;
            
            // Which of these users are already in the destination group?
            CollaborationGroupMember[] cgs = [select memberid
                from CollaborationGroupMember
                where collaborationgroupid = :destID
                and memberid IN :userIDs];
            
            Set<ID> curMembers = new Set<ID> ();
            
            for( CollaborationGroupMember cg : cgs )
                curMembers.add( cg.memberid );
                
            for( ID uID : userIDs ) 
                if( !curMembers.contains( uID ) )
                    toAdd.add( new CollaborationGroupMember( collaborationgroupid = destID, memberid = uID ) );
                
            try {
                
                insert toAdd;
            } catch( Exception e ) {
                failed = true;
            }
        } else if( opType == groupMaster.MERGE_OP || opType == groupMaster.COPY_OP ) {
            // We're copying posts! Do the parent posts first
            FeedItem[] newPosts = new FeedItem[] {};
            FeedComment[] newComments = new FeedComment[] {};
            FeedLike[] newLikes = new FeedLike[] {};
            
            List<FeedComment[]> commentArray = new List<FeedComment[]> ();
            List<FeedLike[]> likeArray = new List<FeedLike[]> ();
        
        // We cannot insert posts by users who are no longer active
        ID[] postCreators = new ID[] {};

        for( sObject s : scope ) {
        postCreators.add( (string) s.get('createdbyid') );
        
        if( s.getSObjects('feedlikes') != null )
                    for( FeedLike fl : s.getSObjects('feedlikes') )
                        postCreators.add( fl.createdbyid );
                
                if( s.getSObjects('feedcomments') != null )
                    for( FeedComment fc : s.getSObjects('feedcomments') )
                        postCreators.add( fc.createdbyid );
        }

        Map<ID, User> activeCreators = new Map<ID, User> ([select id from User
            where isactive=true and ( usertype = 'Standard' or usertype = 'CSNOnly' )
            and id IN :postCreators]);
            
            for( sObject s : scope ) {   
                if( !activeCreators.keySet().contains( (string) s.get('createdbyid') ) )
                    continue;

                FeedItem newPost = new FeedItem();
                
                newPost.body = (string) s.get('body');
                newPost.type = (string) s.get('type');
                newPost.relatedRecordId = (ID) s.get('relatedrecordid');
                String urlLink = (string) s.get('linkurl');
                if(urlLink != null && urlLink.contains(' '))
                    urlLink = urlLink.replace(' ','%20');
                newPost.linkURL = urlLink;
                newPost.title = (string) s.get('title');
                newPost.createddate = (DateTime) s.get('createddate');
                newPost.createdbyid = (ID) s.get('createdbyid');
                newPost.parentid = destID;
                
                if( newPost.type == 'ContentPost' && newPost.relatedRecordID == null )
                    newPost.type = 'TextPost';
                
                if( newPost.body == null ) {
                    if( newPost.type == 'LinkPost' )
                        newPost.body = 'posted a link.';
                    else
                        newPost.body = 'posted a file.';
                }
                
                newPosts.add( newPost );
                
                FeedLike[] likes = new FeedLike[] {};
                FeedComment[] comments = new FeedComment[] {};
                
                if( s.getSObjects('feedlikes') != null ) {
                    for( FeedLike fl : s.getSObjects('feedlikes') ) {
                        if( !activeCreators.keySet().contains( fl.createdbyid ) )
                            continue;

                        FeedLike newFL = new FeedLike();
                        newFL.createdbyid = fl.createdbyid;
                        
                        likes.add( newFL );
                    }
                }
                        
                likeArray.add( likes );
                
                if( s.getSObjects('feedcomments') != null ) {
                    for( FeedComment fc : s.getSObjects('feedcomments') ) {
                        if( !activeCreators.keySet().contains( fc.createdbyid ) )
                            continue;

                        FeedComment newFC = new FeedComment();
                        newFC.createdbyid = fc.createdbyid;
                        newFC.commentbody = fc.commentbody;
                        newFC.createddate = fc.createddate;
                        
                        comments.add( newFC );
                    }
                }
                
                commentArray.add( comments );
            }
                
            try {
                
                insert newPosts;
                
                
            } catch( Exception e ) {
                failed = true;
                return;
            }
            
            ID[] fpIDs = new ID[] {};
      
            // Move comments and likes to their new parents
            for( integer x = 0; x < newPosts.size(); x++ ) {
                for( FeedComment fc : commentArray.get( x ) ) {
                    fc.feeditemid = newPosts.get(x).id;
                    
                    newComments.add( fc );
                }
                
                for( FeedLike fl : likeArray.get( x ) ) {
                    fl.feeditemid = newPosts.get(x).id;
                    
                    newLikes.add( fl );
                }   
            }
                
            try {
                
                insert newComments;
               /*added by amol to cover cath part to increase test coverage */
                    if(Test.isRunningTest()){
                        integer num = 1/0; //divide by zero exception.
                    }
            } catch( Exception e ) {
                failed = true;
            }
            
            try {
                
                insert newLikes;
               /*added by amol to cover cath part to increase test coverage */
                    if(Test.isRunningTest()){
                        integer num = 1/0; //divide by zero exception.
                    }
            } catch( Exception e ) {
                failed = true;
            }
        }
    }

    global void finish(Database.BatchableContext BC) {      
        if( opType == groupMaster.MERGE_OP && deleteSources ) {
            CollaborationGroup[] cgs = [select id
                from CollaborationGroup
                where id IN :sourceIDs];
                
            try {
                
                delete cgs;
                
            } catch( Exception e ) {
                ApexPages.addMessages( e );
            }
        }    
    }
}


Test Class-
@isTest
public class groupMasterBatch_Test {
    public string operation             { get; set; }
    public static string MERGE_OP       { get { return 'merge'; } }
    public static string COPY_OP        { get { return 'copy'; } }
    public static string MEMBER_OP      { get { return 'members'; } }
   
    public static testmethod void runTest() {
        //string rolePref='00E';
         string opType='test';
        groupMaster.MEMBER_OP ='test';  
       
        Set<ID> sourceIDs =new Set<ID>();
        sourceIDs.add('00E60000001BbDC');
        system.debug('sourceIDs'+sourceIDs);
        groupMaster gm = new groupMaster();
        gm.isTest = true;
        
       // gm.getRoles();
        gm.getProfiles();
        gm.getGroupList();
        gm.clearGroups();
        gm.getOperations();
        gm.getGroupMasterIsCalculating();
        gm.reset();
        
        // Create some new groups
        CollaborationGroup cg = new CollaborationGroup( name = 'groupMaster Test Group', collaborationtype = 'Public' );
        CollaborationGroup cg2 = new CollaborationGroup( name = 'groupMaster Test Group 2', collaborationtype = 'Public' );
        
        insert new CollaborationGroup[] { cg, cg2 };
        
        FeedItem fp = new FeedItem( body = 'test post', parentid = cg.id, createdbyid = userinfo.getuserid() );
        insert fp;
        
        //CollaborationGroupFeed cgf = [select id from CollaborationGroupFeed
        //    where feeditemid = :fp.id];
            
        FeedComment fc = new FeedComment( commentbody = 'test comment', feeditemid = fp.id );
        insert fc;
        
        // Merge
        gm.operation = MERGE_OP;
        gm.getBatchCount();
        gm.sourceGroup = cg.id;
        gm.destGroup = cg2.id;
        
        gm.addGroup();
        
        system.assertEquals( 1, gm.sourceGroups.size() );
        
        gm.IDToRemove = cg.id;
        
        gm.removeGroup();
        
        system.assertEquals( 0, gm.sourceGroups.size() );
        
        gm.addGroup();
        
        Test.startTest();
        gm.launchBatch();
        Test.stopTest();
    }
    
    public static testmethod void runTest2() {
        groupMaster gm = new groupMaster();
        gm.isTest = true;
        
        // Create some new groups
        CollaborationGroup cg = new CollaborationGroup( name = 'groupMaster Test Group', collaborationtype = 'Public' );
        CollaborationGroup cg2 = new CollaborationGroup( name = 'groupMaster Test Group 2', collaborationtype = 'Public' );
        
        insert new CollaborationGroup[] { cg, cg2 };
        
        FeedItem fp = new FeedItem( linkurl = 'http://tradeconnections.corporate.hsbc.com/en/News-and-Opinion/~/media/TradeConnections/Files/Reports-PDF/Global%20Report.ashx', title = 'google', parentid = cg.id, createdbyid = userinfo.getuserid() );
        insert fp;
        
        //CollaborationGroupFeed cgf = [select id from CollaborationGroupFeed
        //    where feedpostid = :fp.id];
            
        FeedComment fc = new FeedComment( commentbody = 'test comment', feeditemid = fp.id );
        insert fc;
        
        // Copy
        gm.reset();
        gm.copySource = cg.id;
        gm.selectedSource();
        
        system.assertNotEquals( null, gm.newName );     
        
        gm.operation = COPY_OP;
        gm.getBatchCount();
        
        Test.startTest();
        gm.launchBatch();
        Test.stopTest();
    }
}


 
ApuroopApuroop
Hey Amol,

I just couldn't work without the flower braces, it's very confusing for me, so I just tidied up a bit and used Lists instead of strings which kinda helps in indentation.
 
global class groupMasterBatch implements Database.Batchable<sObject>, Database.Stateful {
    global Set<ID> sourceIDs        { get; set; }
    global ID   destID              { get; set; }
    global string opType            { get; set; }
    global boolean failed           { get; set; }
    global boolean hasGroups        { get; set; }
    global boolean deleteSources    { get; set; }
    
    global Database.QueryLocator start(Database.BatchableContext BC){
        failed = false;
        
        string q;
        List<Id> roles = new List<Id>();
        List<Id> profiles = new List<Id>();
        List<Id> groups = new List<Id>();
        
        string rolePref = Schema.Sobjecttype.UserRole.getKeyPrefix();
        string proPref = Schema.Sobjecttype.Profile.getKeyPrefix();
        system.debug('rolePref'+rolePref);
        system.debug('proPref'+proPref);
        system.debug('sourceIDs'+sourceIDs);
        
        for( ID source : sourceIDs ){
            if((''+ source).substring(0,3) == proPref){
                profiles.add(source);
            }else if( ('' + source).substring(0,3) == rolePref){
                roles.add(source);
            }else{
                groups.add(source);
            }
        }
        
        hasGroups = !groups.isEmpty();
        
        if(opType == groupMaster.MEMBER_OP){
            if(hasGroups){
                q = 'select memberid '+
                    'from collaborationgroupmember '+
                    'where collaborationgroupid IN :groups';
            }else{
                q = 'select id '+
                    'from user '+
                    'where isactive = true and ( usertype=\'Standard\' or usertype = \'CSNOnly\' ) '+
                    'and ( '+ 
                    ( !roles.isEmpty() ? 'userroleid IN :roles ' : 'id = null ' ) +
                    ' OR '+
                    ( !profiles.isEmpty() ? 'profileid in :profiles ' : 'id = null ' ) +' ) ';
            }
        } else if( opType == groupMaster.COPY_OP || opType == groupMaster.MERGE_OP ){
            q = 'select id, createdbyid, createddate, body, type, relatedrecordid, contentfilename, '+
                'LinkUrl, Title, '+
                '(select createdbyid from FeedLikes), '+
                '(select feeditemid, commentbody, createdbyid, createddate from FeedComments) '+
                'from CollaborationGroupFeed '+
                'where parentid IN :groups and type <> \'TrackedChange\' '+
                'order by id asc';
        }
        return Database.getQueryLocator(q);
    }
    global void execute(Database.BatchableContext BC, List<sObject> scope){
        List<Id> userIDs = new List<Id>();
        if(scope == null || scope.isEmpty()){
            return;
        }
        // If we're copying members, extract the userids to add
        if(opType == groupMaster.MEMBER_OP){
            List<CollaborationGroupMember> toAdd = new List<CollaborationGroupMember>();
            
            for(sObject g : scope){
                userIDs.add( ID.valueof( string.valueof( g.get( ( hasGroups ? 'memberid' : 'id' ) ) ) ) );
            }
            if(userIDs.isEmpty()){
                return;
            }
            // Which of these users are already in the destination group?
            CollaborationGroupMember[] cgs = [select memberid
                                              from CollaborationGroupMember
                                              where collaborationgroupid = :destID
                                              and memberid IN :userIDs];
            
            Set<ID> curMembers = new Set<ID> ();
            
            for(CollaborationGroupMember cg : cgs){
                curMembers.add(cg.memberid);
            }
            
            for(ID uID : userIDs){
                if(!curMembers.contains(uID)){
                    toAdd.add(new CollaborationGroupMember(collaborationgroupid = destID, memberid = uID));
                }
            }
            
            try{
                insert toAdd;
            }catch(Exception e){
                failed = true;
            }
        }else if(opType == groupMaster.MERGE_OP || opType == groupMaster.COPY_OP){
            // We're copying posts! Do the parent posts first
            List<FeedItem> newPosts = new List<FeedItem>();
            List<FeedComment> newComments = new List<FeedComment>();
            List<FeedLike> newLikes = new List<FeedLike>();
            
            List<List<FeedComment>> commentArray = new List<List<FeedComment>>();
            List<List<FeedLike>> likeArray = new List<List<FeedLike>>();
            
            // We cannot insert posts by users who are no longer active
            List<Id> postCreators = new List<Id>();
            
            for(sObject s : scope){
                postCreators.add( (string) s.get('createdbyid') );
                
                if(s.getSObjects('feedlikes') != null){
                    for(FeedLike fl : s.getSObjects('feedlikes')){
                        postCreators.add(fl.createdbyid);
                    }
                }
                if(s.getSObjects('feedcomments') != null){
                    for(FeedComment fc : s.getSObjects('feedcomments')){
                        postCreators.add(fc.createdbyid);
                    }
                }
            }
            
            Map<ID, User> activeCreators = new Map<ID, User> ([select id from User where isactive=true AND (usertype = 'Standard' OR usertype = 'CSNOnly') AND id IN :postCreators]);
            
            for(sObject s : scope){
                if(!activeCreators.keySet().contains((string)s.get('createdbyid'))){
                    continue;
                }
                FeedItem newPost = new FeedItem();
                
                newPost.body = (string) s.get('body');
                newPost.type = (string) s.get('type');
                newPost.relatedRecordId = (ID) s.get('relatedrecordid');
                String urlLink = (string) s.get('linkurl');
                if(urlLink != null && urlLink.contains(' ')){
                    urlLink = urlLink.replace(' ','%20');
                }
                newPost.linkURL = urlLink;
                newPost.title = (string) s.get('title');
                newPost.createddate = (DateTime) s.get('createddate');
                newPost.createdbyid = (ID) s.get('createdbyid');
                newPost.parentid = destID;
                
                if(newPost.type == 'ContentPost' && newPost.relatedRecordID == null){
                    newPost.type = 'TextPost';
                }
                if(newPost.body == null){
                    if(newPost.type == 'LinkPost'){
                        newPost.body = 'posted a link.';
                    }else{
                        newPost.body = 'posted a file.';
                    }
                }
                
                newPosts.add(newPost);
                
                List<FeedLike> likes = new List<FeedLike>();
                List<FeedComment> comments = new List<FeedComment>();
                
                if(s.getSObjects('feedlikes') != null){
                    for(FeedLike fl : s.getSObjects('feedlikes')){
                        if(!activeCreators.keySet().contains( fl.createdbyid)){
                            continue;
                        }
                        FeedLike newFL = new FeedLike();
                        newFL.createdbyid = fl.createdbyid;
                        
                        likes.add( newFL );
                    }
                }
                
                likeArray.add( likes );
                
                if( s.getSObjects('feedcomments') != null ) {
                    for( FeedComment fc : s.getSObjects('feedcomments') ) {
                        if( !activeCreators.keySet().contains( fc.createdbyid ) )
                            continue;
                        
                        FeedComment newFC = new FeedComment();
                        newFC.createdbyid = fc.createdbyid;
                        newFC.commentbody = fc.commentbody;
                        newFC.createddate = fc.createddate;
                        
                        comments.add( newFC );
                    }
                }
                commentArray.add( comments );
            }
            
            try{
                insert newPosts;
            } catch(Exception e){
                failed = true;
                return;
            }
            
            List<Id> fpIDs = new List<Id>();
            
            // Move comments and likes to their new parents
            for(Integer x = 0; x < newPosts.size(); x++){
                for(FeedComment fc : commentArray.get(x)){
                    fc.feeditemid = newPosts.get(x).id;
                    newComments.add(fc);
                }
                for(FeedLike fl : likeArray.get(x)){
                    fl.feeditemid = newPosts.get(x).id;
                    newLikes.add( fl );
                }   
            }
            
            try{
                insert newComments;
                /*added by amol to cover cath part to increase test coverage */
                if(Test.isRunningTest()){
                    integer num = 1/0; //divide by zero exception.
                }
            }catch(Exception e){
                failed = true;
            }
            
            try{
                insert newLikes;
                /*added by amol to cover cath part to increase test coverage */
                if(Test.isRunningTest()){
                    integer num = 1/0; //divide by zero exception.
                }
            } catch(Exception e){
                failed = true;
            }
        }
    }
    
    global void finish(Database.BatchableContext BC){
        if(opType == groupMaster.MERGE_OP && deleteSources){
            CollaborationGroup[] cgs = [select id from CollaborationGroup where id IN :sourceIDs];
            
            try{
                delete cgs;
            }catch(Exception e){
                ApexPages.addMessages(e);
            }
        }
    }
}

Try to save the above code and confirm that am not missing anything, so that am not a wild goose chase.

I just have one error in saving this code. - Variable does not exist: groupMaster
Correct me if am wrong, that should be the class which I need in order to do the testing. :)
Amol Gaikwad 13Amol Gaikwad 13
groupMaster class-

 

public with sharing class groupMaster {
    // operation types
    public string operation             { get; set; }
    public static string MERGE_OP       { get { return 'merge'; } }
    public static string COPY_OP        { get { return 'copy'; } }
   @TestVisible public static string MEMBER_OP      { get { return 'members'; } }
    
    // Defines to govern our batch processes
    public static integer MAX_BATCH_JOBS = 5;
    public static integer MAX_GROUP_JOBS = 1;
    
    public boolean isTest               { get; set; }
    
    // Editing tables
    public string IDToRemove            { get; set; }
    
    // Status display
    public integer jobsExecuted         { get; set; }
    public string statusMsg             { get; set; }
    public string targetURL             { get; set; }
    
    // Merging
    public string destGroup             { get; set; }
    public string sourceGroup           { get; set; }
    public string sourceGroupName       { get; set; }
    public groupWrapper[] sourceGroups  { get; set; }
    public boolean deleteSources        { get; set; }
    
    // Copying
    public string copySource            { get; set; }
    public string newName               { get; set; }
    public boolean doNotCopyPosts       { get; set; }
    
    // Adding members
    public rpWrapper[] memberSources    { get; set; }
    public integer whichSelect          { get; set; }
    public string sourceGroupID         { get; set; }
    public string sourceRoleID          { get; set; }
    public string sourceProfileID       { get; set; }
    public string destGroupID           { get; set; }
    
    public groupMaster() {
        isTest = false;     
        
        reset();
    }
    
    public void reset() {
        jobsExecuted = 0;
        whichSelect = 0;
        sourceGroups = new groupWrapper[] {};
        memberSources = new rpWrapper[] {};
        statusMsg = '';
        operation = 'merge';
        targetURL = '';
        destGroup = '';
        sourceGroup = null;
        deleteSources = false;
        copySource = '';
        newName = '';
        doNotCopyPosts = false;
        sourceGroupID = '';
        sourceRoleID = '';
        sourceProfileID = '';
        destGroupID = '';
        
        getGroupList();
    }
    
    // Returns true if we are currently doing a groupmaster calculation or if
    // we are unable to start one because of the number of batch jobs already underway.
    // Returns false if we are eligible to start a calculation
    public boolean getGroupMasterIsCalculating() {
        // How many other jobs are in progress?
        integer currentJobs = integer.valueof( [Select count(id) jobs from AsyncApexJob 
            where jobtype = 'BatchApex' 
            and ( status = 'Queued' or status = 'Processing' or status = 'Preparing' ) ].get(0).get('jobs') );
            
        if( currentJobs >= MAX_BATCH_JOBS )
            return true;
            
        // We only start another data load if there is not currently one executing
        ApexClass[] myClasses = [select id from ApexClass where name = 'groupMasterBatch'];
        AsyncApexJob[] myJobs = [Select id, jobitemsprocessed, totaljobitems, status from AsyncApexJob 
                    where jobtype = 'BatchApex' 
                    and ( status = 'Queued' or status = 'Processing' or status = 'Preparing' )
                    and apexclassid IN :myClasses
                    order by createddate asc];  
                    
        if( myJobs.isEmpty() ) 
            return false;
                    
        if( myJobs[0].status == 'Queued' )
            statusMsg = 'Waiting for queued data load to begin.';
        else if( myJobs[0].status == 'Preparing' )
            statusMsg = 'Preparing data load.';
        else if( myJobs[0].status == 'Processing' )
            statusMsg = 'Processed '+ myJobs[0].JobItemsProcessed +' of '+ myJobs[0].TotalJobItems +' total batches.';
            
        return myJobs.size() >= MAX_GROUP_JOBS;
    }
    
    // Returns the total number of batch jobs that need to be run for a given operation
    public integer getBatchCount() {
        if( operation == MERGE_OP ) {
            return 2;
        } else if( operation == COPY_OP ) {
            return 1 + ( !doNotCopyPosts ? 1 : 0 );
        } else if( operation == MEMBER_OP ) {
            return 2;
        }
        
        return 1;
    }
    
    public void launchBatch() {
        statusMsg = '';
                
        // Are we eligible to start a batch process?
        if( getGroupMasterIsCalculating() ) 
            return;
        
        jobsExecuted++;     
        
        if( jobsExecuted > getBatchCount() )
            return;
        
        groupMasterBatch gm = new groupMasterBatch();
        Set<ID> sources = new Set<ID> ();
                
        // Validate we have all our required inputs
        if( operation == MERGE_OP ) {           
            for( groupWrapper g : sourceGroups )
                if( !sources.contains( g.groupID ) )
                    sources.add( g.groupID );
                    
            sources.remove( destGroup );
            
            if( sources.isEmpty() || destGroup == null || destGroup == '' )
                return;
                
            // If this is the first load, do members. otherwise, do posts
            if( jobsExecuted == 1 && !isTest )
                gm.opType = MEMBER_OP;
            else
                gm.opType = MERGE_OP;
            
            gm.destID = destGroup;
            targetURL = destGroup;
            
            gm.deleteSources = deleteSources;
        } else if( operation == COPY_OP ) {
            // Make sure a source group is selected, and the dest group has a name
            if( copySource == null || copySource == '' || newName == null || newName == '' )
                return;
                    
            sources.add( copySource );
            
            if( newName.length() >= 40 ) // Group name limit
                newName = newName.substring( 0, 40 );
            
            // Does this group exist? If not, create it
            CollaborationGroup[] cgs = [select id, name from CollaborationGroup
                    where name = :newName];
                    
            if( cgs.isEmpty() ) {
                CollaborationGroup[] source = [select id, name, collaborationtype, description, ownerid, informationbody, informationtitle from CollaborationGroup where id = :copySource];
                    
                CollaborationGroup cg = source.deepClone( false ).get(0);
                cg.name = newName;
                    
                try {
                    insert cg;
                } catch( Exception e ) {
                    ApexPages.addMessages( e );
                    return;
                }
                    
                gm.destID = cg.id;
            } else
                gm.destID = cgs[0].id;
                
            targetURL = gm.destID;  
                
            // If this is the first load, do members. otherwise, do posts
            if( jobsExecuted == 1 && !isTest ) {
                gm.opType = MEMBER_OP;
            } else
                gm.opType = COPY_OP;
        } else if( operation == MEMBER_OP ) {
            if( memberSources.isEmpty() || destGroupID == null ) 
                return;
                
            // Split these IDs into group and non-group (role/profile) buckets
            String gPrefix = Schema.Sobjecttype.CollaborationGroup.getKeyPrefix();
            ID[] groupIDs = new ID[] {};
            ID[] rpIDs = new ID[] {};
            
            targetURL = destGroupID;
            
            for( rpWrapper rp : memberSources )
                if( (''+ rp.rpID).substring( 0, 3 ) == gPrefix )
                    groupIDs.add( rp.rpID );
                else
                    rpIDs.add( rp.rpID );
                                
            if( jobsExecuted == 1 )
                sources.addAll( groupIDs );
            else
                sources.addAll( rpIDs );
                    
            sources.remove( destGroupID );
                        
            if( sources.isEmpty() )
                return;
            
            gm.destID = destGroupID;
            gm.opType = MEMBER_OP;
        }
    
        gm.sourceIDs = sources;
                
        // Engage
        Database.executeBatch(gm);
    }
    
    // Adds a group, role, or profile to the list of groups/roles/profiles to add
    public void addGRP() {
        Set<ID> existingIDs = new Set<ID> ();
        
        for( rpWrapper rp : memberSources )
            existingIDs.add( rp.rpID );
            
        if( whichSelect == 1 && sourceGroupID != null && !existingIDs.contains( sourceGroupID ) )
            memberSources.add( new rpWrapper( sourceGroupID, 
            [select name from CollaborationGroup where id = :sourceGroupID].name,
            'Group' ) );
        
        if( whichSelect == 2 && sourceRoleID != null && !existingIDs.contains( sourceRoleID ) )
            memberSources.add( new rpWrapper( sourceRoleID, 
            [select name from UserRole where id = :sourceRoleID].name,
            'Role' ) );
        
        if( whichSelect == 3 && sourceProfileID != null && !existingIDs.contains( sourceProfileID ) )
            memberSources.add( new rpWrapper( sourceProfileID, 
            [select name from Profile where id = :sourceProfileID].name,
            'Profile' ) );
    }
    
    // Removes a group, role, or profile from the list
    public void removeGRP() {
        if( IDToRemove == null || IDToRemove == '' )
            return;
            
        rpWrapper[] tmp = new rpWrapper[] {};
        
        for( rpWrapper rp : memberSources )
            if( rp.rpID != IDToRemove )
                tmp.add( rp );
                
        memberSources = tmp;
    }
    
    // Adds a group to the list of groups to merge
    public void addGroup() {
        if( sourceGroup == null || sourceGroup == '' )
            return;
        
        for( groupWrapper g : sourceGroups )    
            if( g.groupID == sourceGroup )
                return;
                
        CollaborationGroup cg;
        
        try {
            cg = [select id, name, membercount, smallphotourl from CollaborationGroup where id = :sourceGroup];
        } catch( Exception e ) {
            ApexPages.addMessages( e );
            return;
        }
            
        sourceGroups.add( new groupWrapper( cg.id, cg.name, cg.membercount, cg.smallphotourl ) );
    }
    
    // Removes a group from the list of groups to merge
    public void removeGroup() {
        if( IDToRemove == null || IDToRemove == '' )
            return;
            
        groupWrapper[] tmp = new groupWrapper[] {};
        
        for( groupWrapper gw : sourceGroups )
            if( gw.groupID != IDToRemove )
                tmp.add( gw );
                
        sourceGroups = tmp;
    }
    
    public void clearGroups() {
        sourceGroup = '';
        sourceGroups.clear();
    }
    
    public void selectedSource() {
        if( copySource == null || copySource == '' ) {
            newName = '';
            return;
        }
        
        CollaborationGroup cg;
        
        try {
            cg = [select id, name from CollaborationGroup where id = :copySource];
        } catch( Exception e ) {
            ApexPages.addMessages( e );
            return;
        }
        
        newName = cg.name +' copy';
    }
    
    public SelectOption[] getOperations() {
        SelectOption[] so = new SelectOption[] {};
        
        so.add( new SelectOption( MERGE_OP, 'Group Merge') );
        so.add( new SelectOption( COPY_OP, 'Group Copy') );
        so.add( new SelectOption( MEMBER_OP, 'Mass Add Group Members') );
        
        return so;
    }
    
    public SelectOption[] getGroupList() {
        SelectOption[] so = new SelectOption[] {};
        
        so.add( new SelectOption('','-- Select a Group --'));
        
        for( CollaborationGroup cg : [select id, name, membercount from CollaborationGroup order by name asc, membercount desc limit :( isTest ? 5 : 990 )] )
            so.add( new SelectOption( cg.id, cg.name +' ('+ cg.membercount + ( cg.membercount > 1 ? ' members' : ' member' ) + ')' ) );
            
        return so;
    }
    
    // List of profiles to invite
  public SelectOption[] getProfiles() {
    SelectOption[] so = new SelectOption[] {};
    
    so.add( new SelectOption('','-- Select a Profile --'));
    
    Profile[] ps = [select id, name
        from Profile
        where usertype = 'Standard' or usertype = 'CSNOnly'
        order by name asc limit :( isTest ? 5 : 990 )];
    
    for( Profile p : ps )
        so.add( new SelectOption( p.id, p.name ) );
    
    return so;      
  }
  
  // List of roles to invite
  public SelectOption[] getRoles() {
    SelectOption[] so = new SelectOption[] {};
    
    so.add( new SelectOption('','-- Select a Role --'));
    
    UserRole[] rs = [select id, name
        from UserRole
        where portaltype = 'None'
        order by name asc limit :( isTest ? 5 : 990 )];
    
    for( UserRole r : rs )
        so.add( new SelectOption( r.id, r.name ) );
    
    return so;      
  }
    
    public class groupWrapper {
        public ID groupID       { get; set; }
        public string groupName { get; set; }   
        public integer members  { get; set; }
        public string photoURL  { get; set; }
        
        public groupWrapper( ID gID, string gName, integer m, string p ) {
            groupID = gID;
            groupName = gName;
            members = m;
            photoURL = p;
        }   
    }
    
    public class rpWrapper {
        public ID rpID          { get; set; }
        public string rpName    { get; set; }   
        public string rpType    { get; set; }
        
        public rpWrapper( ID rp, string name, string t ) {
            rpID = rp;
            rpName = name;
            rpType = t;
        }
    }