Apex remove sensitive data from json

When you need to remove sensitive data from json before logging the following method will remove a predefined list of keywords

String bodyArgs = '{"name":"test", "ssn":"324234234", "email":"test@mail.com"}';
Object bodyObj = (Object)JSON.deserializeUntyped(bodyArgs);

Map<String, Object> mapObj = new Map<String, Object>();
if (bodyObj instanceof List<Object>){
	List<Object> lstObjs = (List<Object>)JSON.deserializeUntyped(bodyArgs);
    for (Object lstObj : lstObjs){
       Map<String,Object> parseLstObj = (Map<String,Object>)JSON.deserializeUntyped(JSON.serialize(lstObj));
       mapObj.putAll(parseLstObj);
    }
} else {
	mapObj = (Map<String,Object>)JSON.deserializeUntyped(bodyArgs);
}

Map<String, String> newMappedValues = new Map<String, String>
System.debug(removeAttributes(mapObj, newMappedValues));
>>> Output: '{"name":"test"}'

removeAttributes method will iterate all the keys in the payload and remove the sensitive keys from the payload

private Set<String> removeSensitiveKeyValue = new Set<String>{'ssn', 'email', 'dob'};

public Map<String, String> removeAttributes(Map<String,Object> jsonObj, Map<String, String> mappedKeys)  {
	for(String key : jsonObj.keySet()) {
		if (removeSensitiveKeyValue.contains(key)){
			jsonObj.remove(key);
		} else {
	      if(jsonObj.get(key) instanceof Map<String,Object>) {
	          removeAttributes((Map<String,Object>)jsonObj.get(key), mappedKeys);
	      } else if(jsonObj.get(key) instanceof List<Object>) {
	          for(Object listItem : (List<Object>)jsonObj.get(key)) {
	           if(listItem instanceof Map<String,Object>)  {
	        	removeAttributes((Map<String,Object>)listItem, mappedKeys);
	           }
	         }
	      } else {
			mappedKeys.put(key, String.valueOf(jsonObj.get(key)));
		  }
	  	}
	}
	return mappedKeys;
}

Apex Coding Challenge for Iterating Lists

Here is the problem statement:

Given an expectedSum value find the sum (n+ (n+1) = expectedValue) from a list that equals the expectedSum. I am using the following list

1,3,4,4,5,9

and looking for the sum to be 8 (3+5, 4+4)

*Note the list is sorted

Solution 1:


        Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);
		
		for (Integer k=0; k<listInts.size();k++){
			for (Integer j=k+1; j<listInts.size();j++){
				if (listInts.get(k)+listInts.get(j)==expectedSum){
					System.debug(listInts.get(k) + ' + ' + listInts.get(j));
				}
			}
		}	

The above time complexity for a nested for loop is O(n^2)

Solution 2:

Do a binary search to search if the diff is contained in the list


		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);
		for (Integer listInt : listInts){ 
			Integer diffInt = expectedSum-listInt;
			if (binarySearch(listInts, diffInt)!=null){
				System.debug(listInt + " + " + diffInt);
			}
		}
	
	
	public static Integer binarySearch(List<Integer> listInts, Integer searchInt){
		Integer startPos = 0;
		Integer listSize = listInts.size() - 1;
		Integer mid;
		while(startPos <= listSize){
			mid=(startPos+listSize)/2;
			if(listInts.get(mid) == searchInt){
				 return listInts.get(mid);
			}     
			else if(searchInt < listInts.get(mid)){
				listSize = mid - 1;
			}    
			else{
				startPos = mid + 1;
			}
		}
		return null;	 
	}

the above time complexity for binary search is for each element in the array O(n log n) Returning null is not a good practice try to return another number or throw an exception.

Solution 3:

Check if the diff is contained in the list by using the .contains method


		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);

		for (Integer listInt : listInts){
			Integer diffInt =expectedSum-listInt;
			if (listInts.contains(diffInt)){
				System.debug(listInt + ' + ' + diffInt);
			}
		}

the above time complexity for loop is O(n)

Option 4

Start on either end of the array and move inwards when you find a solution, if the sum of the outer and inner element is bigger that the expectedSum move the maxPointer inwards, if it is smaller move the minPointer inwards.

        Integer expectedSum = 1;
		List<Integer> listInts = Arrays.asList(1,3,4,4,5,9);

		Integer maxPointer = listInts.size()-1;
		Integer minPointer = 0;
		for (Integer k=0; k<listInts.size();k++){
			if (expectedSum < listInts.get(maxPointer)){
				maxPointer-=1;
			} else if (minPointer!=maxPointer){
				Integer sumPair =listInts.get(minPointer) + listInts.get(maxPointer);
				if ( sumPair == expectedSum){
					System.debug(listInts.get(minPointer) + " + " + listInts.get(maxPointer));
					minPointer +=1;
					maxPointer-=1;
				} else if (sumPair < expectedSum){
					minPointer +=1;
				} else {
					maxPointer-=1;;
				}
			}
			
		}

the above time complexity for loop is O(n)

Output:

4 + 4
5 + 3
3 + 5

Bonus:

How to do it with an unsorted list:

Iterate through the list and add the integer to the list, if the diff of the expected sum and integer is contained in the set then print it

		Integer expectedSum = 8;
		List<Integer> listInts = Arrays.asList(7,4,6,1,5,2,3);

		Set<Integer> intSetWithDiff = new HashSet<>();
		for (Integer listInt : listInts){
			Integer sumDiff = expectedSum - listInt;
			
			if (intSetWithDiff.contains(sumDiff)){
				System.debug(listInt + " + " + sumDiff);
			}
			intSetWithDiff.add(listInt);		
		}

Output:

1 + 7
2 + 6
3 + 5

Salesforce Object Level and Field Level Security Architecture

The Salesforce Security review require that both:

  1. Object Level (OLS)
  2. Field Level (FLS)

Security is applied for the following areas:

  1. Query of data – Selectors
  2. Triggers – Domains
  3. DML – Unit of Work

To check both (OLS) and FLS we can use fflib_SecurityUtils it has all the to methods to check if a user can access an object and also individual fields for that object. An Apex exception is thrown if the user does not have access to the given object and/or fields.

Example is when a community user tries to create an Account the following error occurs if OLS is enabled

fflib_SecurityUtils.CrudException: You do not have permission to insert Account
Class.fflib_SecurityUtils.checkObjectIsInsertable: line 309, column 1
Class.fflib_SObjectUnitOfWork.insertDmlByType: line 583, column 1
Class.fflib_SObjectUnitOfWork.doCommitWork: line 531, column 1
Class.fflib_SObjectUnitOfWork.commitWork: line 509, column 1
Class.App_Service_Test.testSecurityUOW: line 81, column 1

  1. Query of data – Selectors

The key purpose of this class is to make building dynamic SOQL queries safer and more robust than traditional string concatenation or String.format approaches. It also has an option to automatically check read security for the objects and fields given to it. Here are some of the methods we use to dynamically build all our queries.

Enable FLS check for queries before they run, by setting setEnforceFLS(true)

OR set the OLS and FLS when constructing a new Selector so it will be implement for all queries

OpportunitiesSelector oppsSelector = 
new OpportunitiesSelector(includeFieldSetFields, enforceObjectSecurity, enforceFLS);

2. Triggers – Domains

Domain classes has minimal functionality in it other than the routing of trigger events to the applicable virtual methods and object security enforcement.

Domain classes by OLS by default as part of it’s Configuration class:

3. DML – Unit of Work

This is not currently implemented to check the following:

1. Insert

  if (enforceOLS){
       fflib_SecurityUtils.checkObjectIsInsertable(sObjectType);
  }

2. Update

 if (enforceOLS){
       fflib_SecurityUtils.checkObjectIsUpdateable(sObjectType);
  }


3. Delete 

if (enforceOLS){
     fflib_SecurityUtils.checkObjectIsDeletable(m_sObjectTypes[objectIdx--]);
  }

Disadvantages of OLS and FLS implemented across your app

OLS – this is a check that is done to see if the user profile has access to do CRUD on the specific object. If this check is not done and user tries to do any of the CRUD operations it will just result in a Salesforce error. Doing this check on an object level is not too big of an overhead. If these checks are not done, Permission errors will be thrown as the profile does not have the correct permission.

FLS – this is not recommended as we have to iterate through every field of the object and check if the user has the privileges to read/update/insert/delete to that field. This slows down operations down drastically and only needs to be used in a few uses cases.

Possible solutions run security checks on app

Have a way to enable both OLS and FLS in your code then run through all your test and see if any break. If the break GREAT fix your Object Settings/FLS so that they work. When running in Production have a way to disable this as it may/will slow down your operations/services.

Apex Interview Question > FizzBuzz

Consider the following problem:

Write a short program that prints each number from 1 to 100 in one line

For each multiple of 3, print “Fizz” instead of the number.

For each multiple of 5, print “Buzz” instead of the number.

For numbers which are multiples of both 3 and 5, print “FizzBuzz” instead of the number.
Write a solution (or reduce an existing one) so it has as few characters as possible.

Solution 1: For loop with multiple if statements

String str = '';
for(Integer i = 1; i&amp;lt;=100; i++){
    if(Math.mod(i,3)==0)
        str+='Fizz';
    if(Math.mod(i,5)==0)
        str+='Buzz';
    else if(Math.mod(i, 3) != 0)
		str+=i;
    str +=',';
}
System.debug(str);

Output

1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzz,16,17,Fizz,19,
Buzz,Fizz,22,23,Fizz,Buzz,26,Fizz,28,29,FizzBuzz,31,32,Fizz,34,Buzz,
Fizz,37,38,Fizz,Buzz,41,Fizz,43,44,FizzBuzz,46,47,Fizz,49,Buzz,
Fizz,52,53,Fizz,Buzz,56,Fizz,58,59,FizzBuzz,61,62,Fizz,64,Buzz,
Fizz,67,68,Fizz,Buzz,71,Fizz,73,74,FizzBuzz,76,77,Fizz,79,Buzz,
Fizz,82,83,Fizz,Buzz,86,Fizz,88,89,FizzBuzz,91,92,Fizz,94,Buzz,
Fizz,97,98,Fizz,Buzz

Apex FeedItem Trigger Share to Community

When uploading a new FeedItem we want to share it to a specific community. First thing we need to do is share the FeedItem to a community by sharing it to the User. As the documentation states: Only feed items with a Group or User parent can set a NetworkId or a null value for NetworkScope.

https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_feeditem.htm

NetworkId—The ID of the community in which the FeedItem is available. If left empty, the feed item is only available in the default community.

FeedItemTrigger Trigger

trigger FeedItemTrigger on FeedItem (after insert, before insert) {
    fflib_SObjectDomain.triggerHandler(App_Domain_FeedItem.class);
}

FeedItemTrigger Domain Class

public class App_Domain_FeedItem extends fflib_SObjectDomain {

	public override void onBeforeInsert(){
		List<ContentDocumentLink> contentDocumentLinksList = new List<ContentDocumentLink>();
		for(FeedItem record : (List<FeedItem>) Records){
			record.Body = record.ParentId;
			record.NetworkScope = {NetworkId};
			record.ParentId = UserInfo.getUserId();
			record.Visibility='AllUsers';
		}
	}
}

Now that the file is shared with the user we can give View sharing back to the original object using ContentDocumentLink by using the RelatedRecordId

public class App_Domain_FeedItem extends fflib_SObjectDomain {
public override void onAfterInsert(){
		List<ContentDocumentLink> contentDocumentLinksList = new List<ContentDocumentLink>();
		List<Documents__c> bpDocumentsList = new List<Documents__c>();

		Map<Id, Id> feedItemToRelatedRecordIdMap = new Map<Id, Id>();
		for(FeedItem record : (List<FeedItem>) Records){
			feedItemToRelatedRecordIdMap.put(record.Id, record.RelatedRecordId);
		}

		Map<Id, Id> mapOfRelatedRecordContentVersionMap = new Map<Id, Id>();
		List<ContentVersion> contentDocumentVersions = [Select Id, ContentDocumentId from ContentVersion where id IN :feedItemToRelatedRecordIdMap.values()];
		for (ContentVersion contentDocumentVersion : contentDocumentVersions){
			mapOfRelatedRecordContentVersionMap.put(contentDocumentVersion.Id, contentDocumentVersion.ContentDocumentId);
		}

		for(FeedItem record : (List<FeedItem>) Records){
			String bpDocumentId = record.Body;

			Id relatedRecordId = feedItemToRelatedRecordIdMap.get(record.Id);
			Id contentDocumentId = null;
			if (mapOfRelatedRecordContentVersionMap.containsKey(relatedRecordId)){
				 contentDocumentId = mapOfRelatedRecordContentVersionMap.get(relatedRecordId);
			}

			if (contentDocumentId!=null){
				ContentDocumentLink cdl = new ContentDocumentLink(LinkedEntityId = bpDocumentId , ContentDocumentId=contentDocumentId	, shareType = 'V');
				contentDocumentLinksList.add(cdl);
			}

			Documents__c updateDocument = new Documents__c(Id=bpDocumentId);
			updateDocument.Document_Status__c='Downloaded';
			String formatDownloadUrl = '/CommunityApi/sfc/servlet.shepherd/version/download/{0}?asPdf=false&operationContext=CHATTER';
			updateDocument.Download_Url__c = String.format(formatDownloadUrl, new List<String>{contentDocumentId});
			updateDocument.File_Id__c = contentDocumentId;
			updateDocument.File_Name__c = record.Title;
			updateDocument.File_Size__c = record.ContentSize;
			updateDocument.File_Type__c = record.ContentType!=null ? mimeTypeMap.containsKey(record.ContentType.toLowerCase()) ? mimeTypeMap.get(record.ContentType.toLowerCase()) : record.ContentType  : record.ContentType;
			updateDocument.File_Permission__c='R';

			bpDocumentsList.add(updateDocument);
		}

		if (!contentDocumentLinksList.isEmpty()){
			insert contentDocumentLinksList;
		}

		if (!bpDocumentsList.isEmpty()){
			update bpDocumentsList;
		}
	}
}

Now the Feeditem is shared both to the user and the object. It can be downloaded by the logged in user.

Apex Test Active Case Assignment Rules Assertion Fails

If you are testing active assignment rules you need to add it as an DMLOptions during testing else your assignment rule(s) will not get called and your assertion(s) may fail.

If you have one active Case Assignment Rule you can use dmlOpts.assignmentRuleHeader.useDefaultRule = true else you need to query for AssignmentRules. To query AssignmentRules you will need to have without sharing set as your class level access, else you will not get access to AssignmentRules.

If you have one method to insertCases you can make sure your assignmentRules run every time you execute a test class.

  public Id insertCase(Case bpCase){
      fflib_ISObjectUnitOfWork uow = Rest_App.UnitOfWork.newInstance();

      if (Test.isRunningTest()){
        Database.DMLOptions dmlOpts = new Database.DMLOptions();
         dmlOpts.assignmentRuleHeader.useDefaultRule = true;
         bpCase.SetOptions(dmlOpts);
      }

      uow.registerNew(bpCase);
      uow.commitWork();
      return bpCase.Id;
}

Here is the actual test class:

	@isTest(SeeAllData=true) static void testClientRequest(){
		RestRequest req = new RestRequest();
		RestResponse res = new RestResponse();
		Map<String, String> mapOfIds = App_Global_Test.setupCommunityUserReturnIds();

		test.startTest();
			System.runAs(new User(Id=mapOfIds.get('UserId'))){
				req.requestBody = Blob.valueOf('{"contactId":"'+mapOfIds.get('ContactId')+'", "type":"Client Request", "description":"Please help me with my account", "subject":"Client Request", "reason":"Client Request", "origin":"App"}');
				req.requestURI = '/v1/support/case';
				req.httpMethod = 'POST';
				RestContext.request = req;
				RestContext.response = res;

				App_Rest_Dispatcher.doPost();
				System.assert(res.responseBody!=null);
				System.assertEquals(res.statusCode, 200);
		 }
		test.stopTest();

		List<Case> supportCase = [Select Id, Subject, Description, Type, Owner.Name from Case where ContactId=:mapOfIds.get('ContactId')];
		System.assertEquals(supportCase.size(), 1);
		System.assertEquals(supportCase.get(0).Subject, 'Client Request');
		System.assertEquals(supportCase.get(0).Type, 'Client Request');
		System.assertEquals(supportCase.get(0).Owner.Name, 'Client Services Queue');
	}

Apex Run multiple batch jobs sequentially

We can run 3 batch jobs sequentially by incrementing the jobCounter and passing the integer (job index) into the batch scope

This can be increased to any amount of batch jobs, the problem I solved was able to update the Contact and disable Users in the same code running as different batch job.

As you cannot call @future in a batch method this solves by running each update in their own batch = transaction 👍🏻

global with sharing class App_Job_Account_Delete implements System.Schedulable, Database.Batchable<Integer>, Database.Stateful, Database.AllowsCallouts  {

    private List<JobError> jobErrors = new List<JobError>();

    global Integer jobCounter = 1;

    public void execute(SchedulableContext sc) {
        Database.executeBatch(this, jobCounter);
    }

    public Integer[] start(Database.BatchableContext context) {
        return new Integer[] {jobCounter};
    }

    public void execute(Database.BatchableContext context, Integer[] scope)     {
        try{
            if (isSandbox() || Test.IsRunningTest()){
                if(scope[0] == 1) {
                    //add your code here to run as part of 1st batch
                } else if (scope[0] == 2){
                    //add your code here to run as part of 2nd batch
                }  else if (scope[0] == 3){
                    //add your code here to run as part of 3rd batch
                }
            } else {
                JobError jobError = new JobError();
                jobError.message = 'Environment Error: Job will only run on dev and automation environment';
                jobError.records = new List<SObject>();
                jobErrors.add(jobError);
            }
        } catch (Exception ex){
            JobError jobError = new JobError();
            jobError.records = new List<SObject>();
            jobError.message = 'Exception: ' + ex.getTypeName() + ': ' + ex.getMessage()  + ' -- ' + ex.getCause();
            jobErrors.add(jobError);
        }
    }

    public void finish(Database.BatchableContext context){
        if (jobCounter<3){
            jobCounter++;
            Database.executeBatch(this, jobCounter);
        }
    }

    public class JobError{
        public String message;
        public List<SObject> records;
    }

    public void setJobError(List<JobError> jobErrors){
        this.jobErrors = jobErrors;
    }

    public static Boolean isSandbox() {
        if ([SELECT IsSandbox FROM Organization LIMIT 1].IsSandbox && ('Dev'.equalsIgnoreCase(CustomSettings.Environment__c) || 'Automation'.equalsIgnoreCase(CustomSettings.Environment__c))){
            return true;
        } else {
            return false;
        }
    }
}

Run the code as

Id batchprocessid = Database.executeBatch(new App_Job_Account_Delete());

Apex Comparator compare multiple object fields

Sorting a list of Analysis messages first by boolean and then integer. First we will order by condition and then order. All records where condition is true is will be on top in descending order on top followed by all false condition in descending order.

SummaryAnalysisMessages object to sort

public class SummaryAnalysisMessages {
		private String title;
		private String description;
		private Integer order;
		private Boolean condition;

		public SummaryAnalysisMessages(String title, String description, Integer order, Boolean condition){
			this.title = title;
			this.description = description;
			this.order = order;
			this.condition = condition;
		}

		public Boolean getCondition(){
			return condition;
		}

		public Integer getOrder(){
			return order;
		}
	}

Compare object by condition and then order

public class SummaryAnalysisMessagesCompare extends App_Comparator {
    public override Integer compare(Object a, Object b) {
			SummaryAnalysisMessages aSummaryMessage = (SummaryAnalysisMessages)a;
			SummaryAnalysisMessages bSummaryMessage = (SummaryAnalysisMessages)b;

      Integer summaryMessage1 = aSummaryMessage.getCondition() ? 1 : 0;
      Integer summaryMessage2 = bSummaryMessage.getCondition() ? 1 : 0;

			Integer compareInt = summaryMessage2 - summaryMessage1;
      if (compareInt == 0) {
          compareInt = aSummaryMessage.getOrder() - bSummaryMessage.getOrder();
      }
      return compareInt;
    }
}

Test class to test order

@isTest static void testSummaryAnalysisMessagesCompare(){
		SummaryAnalysisMessages summaryAnalysisMessage1 = new App_Chart.SummaryAnalysisMessages('1', '', 1, false);
		SummaryAnalysisMessages summaryAnalysisMessage2 = new App_Chart.SummaryAnalysisMessages('2', '', 2, true);
		SummaryAnalysisMessages summaryAnalysisMessage3 = new App_Chart.SummaryAnalysisMessages('3', '', 3, false);
		SummaryAnalysisMessages summaryAnalysisMessage4 = new App_Chart.SummaryAnalysisMessages('4', '', 4, true);

		List<SummaryAnalysisMessages> assetAllocationSummaryList = new List<SummaryAnalysisMessages>{summaryAnalysisMessage1, summaryAnalysisMessage2, summaryAnalysisMessage3, summaryAnalysisMessage4};

		App_Comparator.sort(assetAllocationSummaryList, new App_Chart.SummaryAnalysisMessagesCompare());
		System.assertEquals(assetAllocationSummaryList.get(0).getOrder(), 2);
		System.assertEquals(assetAllocationSummaryList.get(1).getOrder(), 4);
		System.assertEquals(assetAllocationSummaryList.get(2).getOrder(), 1);
		System.assertEquals(assetAllocationSummaryList.get(3).getOrder(), 3);
	}

Apex DocumentLink Trigger to Share Documents to Community Contacts

Trigger to check if the custom object has the correct file shared to community user

public without sharing class Domain_Documents extends fflib_SObjectDomain {

	public Domain_Documents(List<Documents__c> records) {
		super(records);
	}

	public class Constructor implements fflib_SObjectDomain.IConstructable{
		public fflib_SObjectDomain construct(List<SObject> sObjectList){
			return new Domain_Documents(sObjectList);
		}
	}

	public override void onAfterUpdate(Map<Id,SObject> existingRecords){
		List<ContentDocumentLink> contentDocumentLinksList = new List<ContentDocumentLink>();
		for (Documents__c documents : (List<Documents__c>) Records){
			if (documents.File_Id__c!=null){
				Documents__c existingDocument = (Documents__c)existingRecords.get(documents.Id);
				if (existingDocument.File_Id__c==null){
					List<ContentDocumentLink> contentDocumentLinkExists = [select ContentDocumentId, Id, IsDeleted, LinkedEntityId, ShareType, SystemModstamp, Visibility from ContentDocumentLink where ContentDocumentId=:documents.File_Id__c];
					Id userToShareWith = [Select Client__r.User__c from Documents__c where Id=:documents.Id].Client__r.User__c;
					Boolean flagToAddToDocs = true;
					for (ContentDocumentLink contentDocumentLinkExist : contentDocumentLinkExists){
						if (contentDocumentLinkExist.LinkedEntityId==userToShareWith){
							flagToAddToDocs=false;
						}
					}
					if (flagToAddToDocs){
						ContentDocumentLink cdl = new ContentDocumentLink(LinkedEntityId = userToShareWith , ContentDocumentId=documents.File_Id__c, shareType = 'C');
						contentDocumentLinksList.add(cdl);
					}
				 }
             }
		}

		if (!contentDocumentLinksList.isEmpty()){
			insert contentDocumentLinksList;
		}
	}
}

Test class to check that sharing is done correctly

@isTest(SeeAllData=true) static void testUpdate() {
		User communityUser = Global_Test.setupCommunityUserAndLogin();

		Documents__c bpDocument = (Documents__c)SmartFactory.createSObject('Documents__c');
		bpDocument.OwnerId = communityUser.Id;
		bpDocument.File_Name__c='Test';
		bpDocument.Document_Status__c = 'New';
		bpDocument.Client__c = communityUser.ContactId;
		insert bpDocument;

		String yourFiles = 'Some file content here';

		ContentVersion conVer = new ContentVersion();
		conVer.ContentLocation = 'S'; // S specify this document is in SF, use E for external files
		conVer.PathOnClient = 'test.txt'; // The files name, extension is very important here which will help the file in preview.
		conVer.Title = 'Test file '; // Display name of the files
		conVer.VersionData = EncodingUtil.base64Decode(yourFiles); // converting your binary string to Blog
		conVer.NetworkId=[Select Id from Network where Status='Live'][0].Id; //current Live community, should just be 1 but could have many
		insert conVer;

		Id conDoc = [SELECT ContentDocumentId FROM ContentVersion WHERE Id =:conVer.Id].ContentDocumentId; //query for contentDocumentId
	 	ContentDocumentLink cDe = new ContentDocumentLink();
		cDe.ContentDocumentId = conDoc;
		cDe.LinkedEntityId = bpDocument.Id; // you can use objectId,GroupId etc
		cDe.ShareType = 'C';
		cDe.Visibility = 'AllUsers';
		insert cDe;

		bpDocument.File_Id__c = conDoc;
		update bpDocument;

		Test.startTest();
			System.runAs(communityUser){
				bpDocument.File_Name__c='Update Name of File';
				update bpDocument;
			}
		Test.stopTest();

		Documents__c queryBpDocuments = [Select Id, File_Name__c, Document_Type__c from Documents__c where Id=:bpDocument.Id][0];
		System.assertEquals(queryBpDocuments.File_Name__c, 'Update Name of File');
	}

Apex generate a hash value for encrypting and decrypting emails

String cryptoSalt='249D5EC76175B12A';
Blob cryptoKey=Blob.valueof('J@NcRfUjXn2r5u8x/A?D(G-KaPdSgVkY');
Blob cryptoIv =Blob.valueof('4t6w9z$C&F)J@NcR');

Blob data = Blob.valueOf('youremail@gmail.com'+cryptoSalt);
Blob encryptedData = Crypto.encrypt('AES256', cryptoKey, cryptoIv, data);
String encodeDataHex = EncodingUtil.convertToHex(encryptedData);
System.debug('>>> encodeDataHex >>> ' + encodeDataHex);
//92939c472350e797aca42206a1b1d90f3b33bd1dbbac23af5fba40866515ef43e11e35edd354d341253bf89eb4094f2c

String encodeData = EncodingUtil.base64Encode(encryptedData);
System.debug('>>> encodeData >>> ' + encodeData);
//kpOcRyNQ55espCIGobHZDzszvR27rCOvX7pAhmUV70PhHjXt01TTQSU7+J60CU8s

// Decrypt the data - the first 16 bytes contain the initialization vector
Blob decodeData = EncodingUtil.base64Decode(encodeData);
Blob decryptedData = Crypto.decrypt('AES256', cryptoKey, cryptoIv, decodeData);

// Decode the decrypted data for subsequent use
String decryptedDataString = decryptedData.toString();
System.debug('>>> ' + decryptedDataString);
//youremail@gmail.com249D5EC76175B12A

%d bloggers like this: