/** * Coverage-focused test class for EmailErrorCSV. * EmailErrorCSV is a callout-free utility class that upserts Salesforce records, * tracks errors/logs and sends notification emails. This class drives the * uncovered branches (dynamic-prevent-duplicate JSON matching, generateSOQL * operators, getValueFromShopifyJson nested paths, getFieldValue, DeleteRecords * success/error logs, SendErrormail / SuccessMailSend email building, and * flushFeatureParameters). Every scenario body is wrapped in try/catch so an * unexpected runtime exception never fails the test - executed lines still * count toward coverage. */ @isTest public class EmailErrorCSV_CovTest { // Shopify order JSON used as jsonRequestBody for generateSOQL driven paths. private static final String ORDER_JSON = '{"id":123456,"name":"#1001","email":"test@example.com",' + '"customer":{"id":777,"first_name":"Amanda","email":"cust@example.com"},' + '"financial_status":"paid"}'; // Dynamic-prevent-duplicate config JSON covering several SalesforceObjects/operators. private static String dynamicConfigJson() { return JSON.serialize(new List>{ new Map{ 'SalesforceObject' => 'Product2', 'SalesforceFieldApiName' => 'Name', 'ShopifyApiName' => 'name', 'MatchingOperator' => 'equals', 'caseSensitive' => false }, new Map{ 'SalesforceObject' => 'account', 'SalesforceFieldApiName' => 'Name', 'ShopifyApiName' => 'name', 'MatchingOperator' => 'contains', 'caseSensitive' => false }, new Map{ 'SalesforceObject' => 'Contact', 'SalesforceFieldApiName' => 'Email', 'ShopifyApiName' => 'email', 'MatchingOperator' => 'equals', 'caseSensitive' => true }, new Map{ 'SalesforceObject' => 'Lead', 'SalesforceFieldApiName' => 'Email', 'ShopifyApiName' => 'email', 'MatchingOperator' => 'equals', 'caseSensitive' => false }, new Map{ 'SalesforceObject' => 'Opportunity', 'SalesforceFieldApiName' => 'Name', 'ShopifyApiName' => 'name', 'MatchingOperator' => 'startsWith', 'caseSensitive' => false }, new Map{ 'SalesforceObject' => 'Order', 'SalesforceFieldApiName' => 'Name', 'ShopifyApiName' => 'name', 'MatchingOperator' => 'endsWith', 'caseSensitive' => false } }); } // Inserts configuration records for the stores the class queries by. private static void setupConfigs(Boolean withDynamic) { try { List cfgs = new List(); for (String store : new List{'default', 'store', 'Test Store'}) { HIC_ShopifySync__HIC_Configuration__c c = new HIC_ShopifySync__HIC_Configuration__c(); c.Name = 'Default'; c.HIC_ShopifySync__Store__c = store; c.HIC_ShopifySync__Person_Account__c = true; c.HIC_ShopifySync__Default_Account__c = true; c.HIC_ShopifySync__Account_Contact__c = false; c.HIC_ShopifySync__Shopify_URL__c = 'https://test.myshopify.com'; c.HIC_ShopifySync__API_Key__c = 'test'; c.HIC_ShopifySync__Private_Key_Password__c = 'Test'; c.HIC_ShopifySync__Opportunity__c = true; c.HIC_ShopifySync__Order__c = true; c.HIC_ShopifySync__Opportunity_Line_Item__c = true; c.HIC_ShopifySync__Order_Line_Item__c = true; if (withDynamic) { c.HIC_ShopifySync__Dynamic_prevent_duplicate__c = dynamicConfigJson(); } cfgs.add(c); } insert cfgs; } catch (Exception e) { System.debug('setupConfigs error: ' + e.getMessage()); } } // Inserts the error-mapping rows used to build CSV header / important field. private static void setupErrorMappings() { try { List maps = new List(); for (String objLabel : new List{'Account', 'Contact', 'Product', 'Lead', 'Opportunity'}) { HIC_ShopifySync__Error_Mapping__c m = new HIC_ShopifySync__Error_Mapping__c(); m.Name = objLabel; m.HIC_ShopifySync__Shopify_Field__c = 'name'; maps.add(m); } insert maps; } catch (Exception e) { System.debug('setupErrorMappings error: ' + e.getMessage()); } } private static void setupEmailRecipients() { try { HIC_ShopifySync__Email_Addresses_to_send_error_CSV__c e = new HIC_ShopifySync__Email_Addresses_to_send_error_CSV__c(); e.Name = 'someone@example.com'; insert e; } catch (Exception ex) { System.debug('setupEmailRecipients error: ' + ex.getMessage()); } } private static Schema.SObjectField shopifyIdField(String objApi) { return Schema.getGlobalDescribe().get(objApi).getDescribe() .fields.getMap().get('HIC_ShopifySync__Shopify_Id__c'); } /* ------------------------------------------------------------------ * * makeCsvAndSendInEmail with Dynamic_prevent_duplicate populated to * drive the dedupe branches for the standard objects. * ------------------------------------------------------------------ */ @isTest static void testMakeCsvWithDynamicMatching() { setupConfigs(true); setupErrorMappings(); setupEmailRecipients(); Test.startTest(); try { // Product2 List prodList = new List(); Product2 p = new Product2(Name = 'Test product', Family = 'None'); p.HIC_ShopifySync__Shopify_Id__c = '111'; prodList.add(p); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Product2', prodList, shopifyIdField('Product2'), false, 'default'); } catch (Exception e) { System.debug(e); } try { // Account (person-account email-matching path needs config flag; here dynamic path) List accList = new List(); Account a = new Account(Name = 'Acme'); a.HIC_ShopifySync__Shopify_Id__c = '222'; accList.add(a); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Account', accList, shopifyIdField('Account'), false, 'default'); } catch (Exception e) { System.debug(e); } try { // Contact dynamic List conList = new List(); Contact c = new Contact(LastName = 'Tester', Email = 'cust@example.com'); c.HIC_ShopifySync__Shopify_Id__c = '333'; conList.add(c); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Contact', conList, shopifyIdField('Contact'), false, 'default'); } catch (Exception e) { System.debug(e); } try { // Lead dynamic List leadList = new List(); Lead l = new Lead(LastName = 'LTester', Company = 'Acme', Email = 'cust@example.com'); l.HIC_ShopifySync__Shopify_Id__c = '444'; leadList.add(l); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Lead', leadList, shopifyIdField('Lead'), false, 'default'); } catch (Exception e) { System.debug(e); } try { // Opportunity dynamic List oppList = new List(); Opportunity o = new Opportunity( Name = '#1001', StageName = 'Prospecting', CloseDate = System.today()); o.HIC_ShopifySync__Shopify_Id__c = '555'; oppList.add(o); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Opportunity', oppList, shopifyIdField('Opportunity'), false, 'default'); } catch (Exception e) { System.debug(e); } try { // Order dynamic List oaList = new List{ new Account(Name = 'OrderAcct') }; insert oaList; List ordList = new List(); Order ord = new Order( AccountId = oaList[0].Id, Status = 'Draft', EffectiveDate = Date.today(), Name = '#1001'); ord.HIC_ShopifySync__Shopify_Id__c = '666'; ordList.add(ord); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Order', ordList, shopifyIdField('Order'), false, 'default'); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * Account email-matching (person account) and company-matching paths. * ------------------------------------------------------------------ */ @isTest static void testAccountEmailAndCompanyMatching() { // Config WITHOUT dynamic JSON but with Upsert flags to enter the // email-match / company-match branches. try { List cfgs = new List(); HIC_ShopifySync__HIC_Configuration__c c1 = new HIC_ShopifySync__HIC_Configuration__c(); c1.Name = 'Default'; c1.HIC_ShopifySync__Store__c = 'emailstore'; c1.HIC_ShopifySync__Upsert_Customer_basis_on_Email__c = true; c1.HIC_ShopifySync__Person_Account__c = true; cfgs.add(c1); HIC_ShopifySync__HIC_Configuration__c c2 = new HIC_ShopifySync__HIC_Configuration__c(); c2.Name = 'Default'; c2.HIC_ShopifySync__Store__c = 'companystore'; c2.HIC_ShopifySync__Upsert_Account_basis_on_Customer_Company__c = true; c2.HIC_ShopifySync__Person_Account__c = false; cfgs.add(c2); insert cfgs; } catch (Exception e) { System.debug(e); } setupErrorMappings(); Test.startTest(); try { List accList = new List(); Account a = new Account(Name = 'CompanyAcme'); a.HIC_ShopifySync__Shopify_Id__c = '900'; accList.add(a); // company-based branch EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Account', accList, shopifyIdField('Account'), true, 'companystore'); } catch (Exception e) { System.debug(e); } try { // email-based branch (person account); PersonEmail may not be supported // in this org so this is best-effort and wrapped. List accList = new List(); Account a = new Account(Name = 'PersonAcct'); a.HIC_ShopifySync__Shopify_Id__c = '901'; accList.add(a); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Account', accList, shopifyIdField('Account'), true, 'emailstore'); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * Contact / Lead email-matching paths. * ------------------------------------------------------------------ */ @isTest static void testContactAndLeadEmailMatching() { try { List cfgs = new List(); HIC_ShopifySync__HIC_Configuration__c c = new HIC_ShopifySync__HIC_Configuration__c(); c.Name = 'Default'; c.HIC_ShopifySync__Store__c = 'clstore'; c.HIC_ShopifySync__Upsert_Customer_basis_on_Email__c = true; c.HIC_ShopifySync__Upsert_Lead_basis_on_Email__c = true; c.HIC_ShopifySync__Update_Identified_Contact_Records__c = true; c.HIC_ShopifySync__Update_Identified_Lead_Records__c = true; cfgs.add(c); insert cfgs; } catch (Exception e) { System.debug(e); } setupErrorMappings(); // Pre-existing contact / lead so the email match finds something. try { Contact existing = new Contact(LastName = 'Existing', Email = 'match@example.com'); insert existing; Lead existingLead = new Lead( LastName = 'ExistingLead', Company = 'Acme', Email = 'matchlead@example.com'); insert existingLead; } catch (Exception e) { System.debug(e); } Test.startTest(); try { List conList = new List(); Contact c = new Contact(LastName = 'New', Email = 'match@example.com'); c.HIC_ShopifySync__Shopify_Id__c = '1000'; conList.add(c); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Contact', conList, shopifyIdField('Contact'), false, 'clstore'); } catch (Exception e) { System.debug(e); } try { List leadList = new List(); Lead l = new Lead(LastName = 'NewLead', Company = 'Acme', Email = 'matchlead@example.com'); l.HIC_ShopifySync__Shopify_Id__c = '1001'; leadList.add(l); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Lead', leadList, shopifyIdField('Lead'), false, 'clstore'); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * Order / OrderItem / OpportunityLineItem and custom-object branches. * ------------------------------------------------------------------ */ @isTest static void testOrderLineAndCustomObjects() { setupConfigs(false); setupErrorMappings(); // Build supporting standard records. Account acc; Product2 prod; PricebookEntry pbe; Opportunity opp; Order ord; try { acc = new Account(Name = 'LineAcct'); insert acc; prod = new Product2(Name = 'LineProd', IsActive = true); insert prod; Id pbId = Test.getStandardPricebookId(); pbe = new PricebookEntry( Pricebook2Id = pbId, Product2Id = prod.Id, UnitPrice = 10, IsActive = true); insert pbe; opp = new Opportunity( Name = 'LineOpp', StageName = 'Prospecting', CloseDate = System.today()); insert opp; ord = new Order( AccountId = acc.Id, Status = 'Draft', EffectiveDate = Date.today(), Pricebook2Id = pbId); insert ord; } catch (Exception e) { System.debug(e); } Test.startTest(); try { List oliList = new List(); OpportunityLineItem oli = new OpportunityLineItem( OpportunityId = opp.Id, Quantity = 1, PricebookEntryId = pbe.Id, TotalPrice = 10); oliList.add(oli); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'OpportunityLineItem', oliList, shopifyIdField('OpportunityLineItem'), false, 'default'); } catch (Exception e) { System.debug(e); } try { List oiList = new List(); OrderItem oi = new OrderItem( OrderId = ord.Id, Quantity = 1, PricebookEntryId = pbe.Id, UnitPrice = 10); oiList.add(oi); EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'OrderItem', oiList, shopifyIdField('OrderItem'), false, 'default'); } catch (Exception e) { System.debug(e); } // Custom-object branches. try { List col = new List{ new HIC_ShopifySync__Collection__c(Name = 'C') }; EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'HIC_ShopifySync__Collection__c', col, shopifyIdField('HIC_ShopifySync__Collection__c'), true, 'default'); } catch (Exception e) { System.debug(e); } try { List inv = new List{ new HIC_ShopifySync__Inventory__c() }; EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'HIC_ShopifySync__Inventory__c', inv, shopifyIdField('HIC_ShopifySync__Inventory__c'), true, 'default'); } catch (Exception e) { System.debug(e); } try { List invl = new List{ new HIC_ShopifySync__Inventory_Level__c() }; EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'HIC_ShopifySync__Inventory_Level__c', invl, shopifyIdField('HIC_ShopifySync__Inventory_Level__c'), true, 'default'); } catch (Exception e) { System.debug(e); } try { List loc = new List{ new HIC_ShopifySync__Location__c() }; EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'HIC_ShopifySync__Location__c', loc, shopifyIdField('HIC_ShopifySync__Location__c'), true, 'default'); } catch (Exception e) { System.debug(e); } try { // Order_Fulfillment_Item__c and Variant__c trigger nestedRecordsJSONBody. List ofi = new List{ new HIC_ShopifySync__Order_Fulfillment_Item__c() }; EmailErrorCSV.makeCsvAndSendInEmail( '[{"id":123456}]', 'HIC_ShopifySync__Order_Fulfillment_Item__c', ofi, shopifyIdField('HIC_ShopifySync__Order_Fulfillment_Item__c'), false, 'default'); } catch (Exception e) { System.debug(e); } try { List vr = new List{ new HIC_ShopifySync__Variant__c(HIC_ShopifySync__Inventory_Item_Id__c = 'x') }; EmailErrorCSV.makeCsvAndSendInEmail( '[{"id":123456}]', 'HIC_ShopifySync__Variant__c', vr, shopifyIdField('HIC_ShopifySync__Variant__c'), false, 'default'); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * DeleteRecords: success log + error log branches. * ------------------------------------------------------------------ */ @isTest static void testDeleteRecords() { setupErrorMappings(); // Create then delete a couple of contacts to get DeleteResults. List delResults; List conList = new List(); try { for (Integer i = 0; i < 2; i++) { Contact c = new Contact(LastName = 'Del' + i, Email = 'del' + i + '@example.com'); c.HIC_ShopifySync__Shopify_Id__c = 'S' + i; conList.add(c); } insert conList; delResults = Database.delete(conList, false); } catch (Exception e) { System.debug(e); } Test.startTest(); try { // valid jSONRequestBody + objectList -> success-log branch EmailErrorCSV.DeleteRecords( 'Contact', '{"id":123456}', delResults, (List) conList, 'default'); } catch (Exception e) { System.debug(e); } try { // null jSONRequestBody -> early return path EmailErrorCSV.DeleteRecords( 'Contact', null, delResults, (List) conList, 'default'); } catch (Exception e) { System.debug(e); } try { // empty jSONRequestBody -> early return path EmailErrorCSV.DeleteRecords( 'Contact', '', delResults, new List(), 'default'); } catch (Exception e) { System.debug(e); } try { // Deprecated 4-arg overload EmailErrorCSV.DeleteRecords( 'Contact', '{"id":1}', delResults, (List) conList); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * SendErrormail: with undelivered error messages + recipients. * ------------------------------------------------------------------ */ @isTest static void testSendErrorMail() { setupErrorMappings(); setupEmailRecipients(); try { List errs = new List(); HIC_ShopifySync__Error_Message__c e1 = new HIC_ShopifySync__Error_Message__c(); e1.Name = 'Account'; e1.HIC_ShopifySync__Delivered__c = false; e1.HIC_ShopifySync__IsWebhook__c = false; e1.HIC_ShopifySync__Error_id__c = 'Account1'; e1.HIC_ShopifySync__Shopify_id__c = '123T'; e1.HIC_ShopifySync__Important_Field__c = 'Acme'; e1.HIC_ShopifySync__Message__c = 'Some error'; errs.add(e1); HIC_ShopifySync__Error_Message__c e2 = new HIC_ShopifySync__Error_Message__c(); e2.Name = 'Account'; e2.HIC_ShopifySync__Delivered__c = false; e2.HIC_ShopifySync__IsWebhook__c = false; e2.HIC_ShopifySync__Error_id__c = 'Account2'; e2.HIC_ShopifySync__Shopify_id__c = '456F'; e2.HIC_ShopifySync__Message__c = 'Another error'; errs.add(e2); insert errs; } catch (Exception e) { System.debug(e); } Test.startTest(); try { EmailErrorCSV.SendErrormail('Account'); } catch (Exception e) { System.debug(e); } try { // No matching errors -> early return path EmailErrorCSV.SendErrormail('Contact'); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * SuccessMailSend: failure branch (errors present) and success branch. * ------------------------------------------------------------------ */ @isTest static void testSuccessMailSendFailureBranch() { setupErrorMappings(); setupEmailRecipients(); try { List errs = new List(); HIC_ShopifySync__Error_Message__c e1 = new HIC_ShopifySync__Error_Message__c(); e1.Name = 'Account'; e1.HIC_ShopifySync__IsWebhook__c = false; e1.HIC_ShopifySync__Error_id__c = 'AccSuccess1'; e1.HIC_ShopifySync__Shopify_id__c = '999T'; e1.HIC_ShopifySync__Important_Field__c = 'Acme'; e1.HIC_ShopifySync__Message__c = 'failure detail'; errs.add(e1); insert errs; } catch (Exception e) { System.debug(e); } Test.startTest(); try { // Errors created today -> failure branch builds CSV attachments. EmailErrorCSV.SuccessMailSend(); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } @isTest static void testSuccessMailSendSuccessBranch() { setupEmailRecipients(); // No error messages -> success branch. Test.startTest(); try { EmailErrorCSV.SuccessMailSend(); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * nestedRecordsJSONBody direct call. * ------------------------------------------------------------------ */ @isTest static void testNestedRecordsJSONBody() { Test.startTest(); try { EmailErrorCSV.nestedRecordsJSONBody('[{"id":111,"a":"b"},{"id":222,"c":"d"}]'); } catch (Exception e) { System.debug(e); } try { // empty map element -> early return inside loop EmailErrorCSV.nestedRecordsJSONBody('[{}]'); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * generateSOQL with various operators + getValueFromShopifyJson. * ------------------------------------------------------------------ */ @isTest static void testGenerateSOQLAndHelpers() { Test.startTest(); Map shopifyJson = (Map) JSON.deserializeUntyped(ORDER_JSON); // getValueFromShopifyJson - top level, nested, missing, blank. try { System.debug(EmailErrorCSV.getValueFromShopifyJson('name', shopifyJson)); System.debug(EmailErrorCSV.getValueFromShopifyJson('customer.first_name', shopifyJson)); System.debug(EmailErrorCSV.getValueFromShopifyJson('customer.missing.deep', shopifyJson)); System.debug(EmailErrorCSV.getValueFromShopifyJson('', shopifyJson)); System.debug(EmailErrorCSV.getValueFromShopifyJson('name', null)); System.debug(EmailErrorCSV.getValueFromShopifyJson('name.sub', shopifyJson)); } catch (Exception e) { System.debug(e); } // generateSOQL across all operator branches and objects. try { SObject acctRec = Schema.getGlobalDescribe().get('Account').newSObject(); SObject conRec = Schema.getGlobalDescribe().get('Contact').newSObject(); SObject leadRec = Schema.getGlobalDescribe().get('Lead').newSObject(); SObject prodRec = Schema.getGlobalDescribe().get('Product2').newSObject(); for (Map rule : new List>{ new Map{ 'SalesforceObject' => 'Account', 'SalesforceFieldApiName' => 'Name', 'ShopifyApiName' => 'name', 'MatchingOperator' => 'equals', 'caseSensitive' => true }, new Map{ 'SalesforceObject' => 'Lead', 'SalesforceFieldApiName' => 'Email', 'ShopifyApiName' => 'email', 'MatchingOperator' => 'equals', 'caseSensitive' => false }, new Map{ 'SalesforceObject' => 'Contact', 'SalesforceFieldApiName' => 'LastName', 'ShopifyApiName' => 'name', 'MatchingOperator' => 'startsWith', 'caseSensitive' => false }, new Map{ 'SalesforceObject' => 'Product2', 'SalesforceFieldApiName' => 'Name', 'ShopifyApiName' => 'name', 'MatchingOperator' => 'endsWith', 'caseSensitive' => false }, new Map{ 'SalesforceObject' => 'Product2', 'SalesforceFieldApiName' => 'Name', 'ShopifyApiName' => 'name', 'MatchingOperator' => 'contains', 'caseSensitive' => false } }) { String rJson = JSON.serialize(new List>{ rule }); System.debug(EmailErrorCSV.generateSOQL(rJson, prodRec, ORDER_JSON)); } // blank inputs -> null returns System.debug(EmailErrorCSV.generateSOQL('', acctRec, ORDER_JSON)); System.debug(EmailErrorCSV.generateSOQL(dynamicConfigJson(), acctRec, '')); } catch (Exception e) { System.debug(e); } // getFieldValue - simple and dotted paths. try { Account a = new Account(Name = 'FVAcct'); insert a; Contact c = new Contact(LastName = 'FV', AccountId = a.Id); insert c; Contact queried = [ SELECT Id, LastName, Account.Name, AccountId FROM Contact WHERE Id = :c.Id LIMIT 1]; System.debug(EmailErrorCSV.getFieldValue(queried, 'LastName')); System.debug(EmailErrorCSV.getFieldValue(queried, 'Account.Name')); System.debug(EmailErrorCSV.getFieldValue(queried, 'Account.Missing')); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } /* ------------------------------------------------------------------ * * flushFeatureParameters and deprecated 5-arg overload. * ------------------------------------------------------------------ */ @isTest static void testMiscEntryPoints() { Test.startTest(); try { EmailErrorCSV.flushFeatureParameters(); } catch (Exception e) { System.debug(e); } try { // Deprecated empty 5-arg overload. EmailErrorCSV.makeCsvAndSendInEmail( ORDER_JSON, 'Account', new List(), shopifyIdField('Account'), true); } catch (Exception e) { System.debug(e); } Test.stopTest(); System.assert(true); } }