SubscriptionService
Epic, User Stories, and Tasks
Epic: Subscription Management for Contacts and Leads
- As a marketing manager,
- I want to manage subscriber information for both contacts and leads,
- So that I can effectively track and communicate with our users and respond to their preferences.
User Stories
User Story 1: Fetch Subscriber Details by ID
- As a Salesforce administrator,
- I want to fetch subscriber details using their ID,
- So that I can confirm subscriber information stored in our system.
Acceptance Criteria:
- GIVEN an existing subscriber ID,
- WHEN I request subscriber details,
- THEN the correct subscriber information is returned.
User Story 2: Fetch Subscriber Details by Email
- As a marketing analyst,
- I want to fetch subscriber details using their email address,
- So that I can analyze the subscriber list and preferences.
Acceptance Criteria:
- GIVEN an existing subscriber email,
- WHEN I request subscriber details,
- THEN the correct subscriber information is returned.
User Story 3: Format Subscriber Data
- As a developer,
- I want subscriber details to be structured properly,
- So that the data can be used efficiently by our applications.
Acceptance Criteria:
- GIVEN a populated Subscriber object,
- WHEN the formatting function is called,
- THEN the data is structured in a predefined format consistent with the expected model.
Technical Tasks
Task 1: Implement fetchById Method
- Description: Develop the
fetchByIdmethod in theSubscriptionServiceclass to retrieve subscriber data by ID from the Contact and Lead objects. - Completion Criteria:
- The method successfully returns a Subscriber object with all relevant fields populated.
- Error handling is included for cases where the subscriber is not found.
Task 2: Implement fetchByEmail Method
- Description: Create the
fetchByEmailmethod to retrieve subscribers based on their email addresses. - Completion Criteria:
- The method returns a list of Subscriber objects corresponding to the provided email.
- Error messages are generated when no subscribers are found.
Task 3: Validate Subscriber Object Structure
- Description: Write unit tests to validate that the Subscriber object is populated with correct data when fetching by ID and email.
- Completion Criteria:
- Test cases confirm all required fields are filled correctly.
- Tests ensure that error handling works as expected and returns proper messages.
Task 4: Error Handling Implementation
- Description: Implement error handling for both
fetchByIdandfetchByEmailmethods to handle database queries that may fail. - Completion Criteria:
- Ensure that meaningful error messages are logged and returned within Subscriber objects when exceptions occur.
Functional Map
Subscription Management
Sub-function 1.1: Subscriber Class
- Variables
- id
- firstName
- lastName
- company
- country
- phone
- newsletter
- slides
- whitepapers
- events
- specialOffers
- weeklyDigest
- subsChanged
- sObjectType
- error
- errorMessage
Sub-function 1.2: Contact to Subscriber Conversion
- Method:
contactToSubscriber(Contact contact)
Sub-function 1.3: Lead to Subscriber Conversion
- Method:
leadToSubscriber(Lead lead)
Sub-function 1.4: Fetch Subscriber by ID
- Method:
fetchById(String Id) - Dependency:
- Calls
contactToSubscriber - Calls
leadToSubscriber
Sub-function 1.5: Fetch Subscriber by Email
- Method:
fetchByEmail(String email) - Dependency:
- Calls
contactToSubscriber - Calls
leadToSubscriber
→ Error Handling - Sub-function 1.6: Handles error states in all functions
Data Retrieval
Sub-function 2.1: Fetch by Contact ID
- Query: Fetches relevant Contact details using Salesforce SOQL
Sub-function 2.2: Fetch by Lead ID
- Query: Fetches relevant Lead details using Salesforce SOQL
→ Subscription Management - Data fetched in sub-functions informs subscription status, preference settings.
Alerts and Notifications
Sub-function 3.1: Event Alerts
- Determines if a subscriber opted in for event notifications based on subscriber data.
Sub-function 3.2: Newsletter Alerts
- Checks if a subscriber has opted out of receiving newsletters.
→ Data Retrieval - Alerts are dependent on data provided in Subscriber objects.
Testing and Validation
Sub-function 4.1: Test Fetch Contact by ID
- Method:
testFetchContactById()
Sub-function 4.2: Test Fetch Lead by ID
- Method:
testFetchLeadById()
Sub-function 4.3: Test Fetch by ID No Match
- Method:
testFetchByIdNoMatch()
Sub-function 4.4: Test Fetch by Email
- Method:
testFetchByEmail()
→ Subscription Management - Testing evaluation flows back into subscription management operations.
General Observations
- Each functional block has dependencies leading back to the Subscription Management domain.
- Data flows through the system, interacting closely with Subscriber preferences and alert settings.
- Error handling is integrated throughout the functions to ensure robustness.
Detailed Functional Specifications
Functional Scope
The SubscriptionService Apex class provides functionalities to manage and manipulate subscriber information related to contacts and leads, specifically handling their subscription preferences. The supported business processes include:
- Subscriber Management: Managing subscribers by retrieving and updating subscription preferences.
- Contact and Lead Data Integration: Integrating contact and lead information to support subscription handling in Salesforce.
Business Process: Subscriber Management
Use Case: Fetch Subscriber by ID
- Main functional domain: Subscriber Management
- Main Actor: System Administrator or Automation Process
- Description: The system retrieves subscriber details using a unique identifier (ID).
- Pre-conditions: The ID must exist in the database (either in the Contact or Lead object).
- Post-conditions: Subscriber information is returned, including error handling if the subscriber is not found.
- Detailed steps:
- The actor calls
fetchById(String Id)method with a valid subscriber ID. - The system attempts to look up the ID in the Contact object first.
- If found, the information is transformed into a
Subscriberobject usingcontactToSubscriber. - If not found in Contacts, the system looks in the Lead object and uses
leadToSubscriberif a lead is found. - If no records are found, an error message is returned in the
Subscriberobject.
Use Case: Fetch Subscribers by Email
- Main functional domain: Subscriber Management
- Main Actor: Marketing Team or System
- Description: The system retrieves all subscribers with a specific email address.
- Pre-conditions: The email must exist in the database.
- Post-conditions: A list of Subscriber objects is returned, which includes subscription preferences.
- Detailed steps:
- The actor calls
fetchByEmail(String email)with a valid email address. - The system attempts to look up contacts sharing that email.
- If found, those contacts are converted to
Subscriberobjects. - If no contacts are found, the system then checks if leads shared the email and converts them to
Subscriberobjects. - The function returns a list of matching
Subscriberobjects, or a single object indicating no results found.
Functionalities Supported by the Class
- Methods:
-
contactToSubscriber(Contact contact): Converts a Contact object into a Subscriber object by mapping Contact fields to Subscriber fields. -
leadToSubscriber(Lead lead): Converts a Lead object into a Subscriber object by mapping Lead fields to Subscriber fields. -
fetchById(String Id):- Initializes a new Subscriber object with the provided ID.
- Tries to fetch a Contact first; if unsuccessful, tries to fetch a Lead.
- Populates the Subscriber fields based on the found record's attributes.
- Sets error messages accordingly if the subscriber is not found.
-
fetchByEmail(String email):- Initializes a list of Subscribers based on provided email.
- Follows a similar logic to
fetchByIdfor Contact and then Leads. - Returns a list of Subscribers or an error message if no matches found.
Detailed Analysis of Business Rules
- Validation Rules:
- The system checks if a contact or a lead exists when fetching by ID or email.
-
If a contact/lead opted out of specific subscriptions (e.g., newsletter, whitepapers), the corresponding property in the Subscriber object is set to
false. -
Data Integrity:
- The
Subscriberobject flags whether an error occurred during fetching, preventing further processing with invalid data. -
The data is mapped directly from Salesforce standard objects (Contact, Lead), ensuring consistency with the existing Salesforce schema.
-
Automations:
- The class methods are intended to be called from external sources, such as APIs or integration tools (potentially utilized by marketing automation platforms) to synchronize subscriber preferences.
Conclusion
This Apex class effectively facilitates subscription management by integrating data from both Contact and Lead Salesforce objects and provides subscribers' relevant subscription states while handling potential errors gracefully. The documented specifications herein should aid development and facilitate understanding for both technical and non-technical stakeholders.
Detailed Technical Specifications
Main Functionality Analysis
-
Purpose: The
SubscriptionServiceclass is designed to manage the subscription details for subscribers, encapsulating data from bothContactandLeadSalesforce objects into a consistentSubscriberobject. -
Role: This class is a utility service that provides web services to convert
ContactandLeadrecords into a standardizedSubscriberformat and allows fetching of subscribers by their identifier or email. -
Triggers: It does not act on the creation, update, or deletion of records directly but instead provides methods that can be invoked externally (e.g., from web services).
-
Business Context and Goal: The class supports business needs regarding subscriber management for newsletters, promotional offers, and event notifications by allowing easy access to subscriber information and enabling modifications through Salesforce.
Method Descriptions
1. contactToSubscriber(Contact contact)
- Description: Converts a
Contactobject into aSubscriberobject. - Parameters:
Contact contact: TheContactrecord to be converted.- Return Value:
Subscriber: A newSubscriberobject populated with information from theContact.- Exceptions:
- No specific exceptions are raised; however, it assumes valid
Contactdata is provided.
2. leadToSubscriber(Lead lead)
- Description: Converts a
Leadobject into aSubscriberobject. - Parameters:
Lead lead: TheLeadrecord to be converted.- Return Value:
Subscriber: A newSubscriberobject populated with information from theLead.- Exceptions:
- No specific exceptions are raised; valid
Leaddata is assumed.
3. fetchById(String Id)
- Description: Fetches a
Subscriberby its Id, searching through bothContactandLeadrecords. - Parameters:
String Id: The Id of the subscriber (either contact or lead).- Return Value:
Subscriber: Details of the subscriber if found, populated with the correspondingContactorLeadinformation.- Exceptions:
- An exception is caught if the subscriber is not found, setting an error flag and message accordingly.
4. fetchByEmail(String email)
- Description: Fetches a list of subscribers based on the provided email.
- Parameters:
String email: The email to search for in bothContactandLeadrecords.- Return Value:
List<Subscriber>: A list ofSubscriberobjects for matching records.- Exceptions:
- Catches exceptions, setting an error flag if an issue arises during the fetch.
Interaction with Other Modules
- Dependencies on External Classes/Objects:
- Uses the
ContactandLeadSalesforce standard objects. -
Relies on Salesforce's database querying capabilities (SOQL) to retrieve objects.
-
Impact on Program Behavior:
- The structure of the
Subscriberclass acts as an intermediary for data captured from theContactandLeadSalesforce objects, simplifying the consumer of this data by providing a uniform data model.
Data Flow Analysis
- Types of Data Handled:
- Handles
ContactandLeadsObjects. -
Processes lists of subscribers as returned from SOQL queries.
-
Data Processing:
-
Data is received through web service calls; the data is validated and transformed by retrieving properties from
ContactorLeadrecords and mapped to theSubscriberobject. -
Data Storage:
- Updates are not stored back in the original
ContactorLeadrecords; rather, the establishedSubscriberobjects are returned to the caller.
Use Cases Covered
- Convert Contact to Subscriber:
-
Provides a mechanism for integrating subscriber details from existing
Contactrecords into a form that can be processed by a subscription service. -
Convert Lead to Subscriber:
-
Similarly allows the conversion of
Leadrecords into subscriber details, thus supporting lead management and subscription notification approaches. -
Fetch Subscriber by Id:
-
Fetches subscriber information for a user interface or backend process based on a unique identifier, regardless of whether the record is from the
ContactorLead. -
Fetch Subscribers by Email:
- Retrieves potentially multiple subscribers based on email, addressing needs where email-based insights or marketing campaigns are required.
These functionalities provide a robust framework for handling subscribers, able to adapt to dynamic business needs regarding user engagement strategies.
Detailed review of Salesforce org and Apex code
Performance and Scalability
Performance Bottlenecks
Issue Identified: The fetchById and fetchByEmail methods use SOQL queries that may lead to performance issues if they are called with multiple IDs or emails in a high-volume context. Repeated queries inside loops lead to inefficient resource usage.
Example:
List<Contact> contacts = [Select Id, FirstName, LastName, Account.Name, MailingCountry, Email, Phone, Subscriptions_changed__c, ... from Contact Where Email = :email];
Recommendation: Optimize these methods by using batch processing or aggregate queries when fetching records. Avoid nested queries, and if possible, query in bulk to reduce the number of SOQL calls. Use collections to store ID values and perform a single query instead of multiple calls.
Scalability
Issue Identified: The static resource-heavy methods (fetchById, fetchByEmail) could become bottlenecks with large datasets due to governor limits.
Recommendation: Implement a caching mechanism or utilize Platform Cache to store frequently accessed subscriber data if applicable. Consider implementing pagination for data-heavy operations to maintain performance.
Security and Compliance
Security Measures
Issue Identified: The code does not implement explicit field-level security checks, risking exposure of sensitive data.
Recommendation: Use with sharing in your classes and implement CRUD/FLS checks on data operations. Use the WITH SECURITY_ENFORCED option in SOQL to ensure the data returned respects the user's permissions.
Example:
List<Contact> contacts = [SELECT Id, ... FROM Contact WHERE Email = :email WITH SECURITY_ENFORCED];
Compliance Requirements
Issue Identified: There is no mention of compliance handling for sensitive user data.
Recommendation: Ensure that any handling of personal data adheres to GDPR/HIPAA guidelines by implementing appropriate data retention policies, disclosure handling, consent management, and handling of user rights related to their data.
Code Quality and Maintainability
Code Readability and Modularity
Issue Identified: The Subscriber class has many individual properties set within the methods, leading to potential redundancy and less clarity.
Recommendation: Refactor the Subscriber class to use a Builder pattern or Factory methods to create instances of Subscriber. This encapsulates the complexity and improves readability.
Example:
public class SubscriberBuilder {
private Subscriber subscriber = new Subscriber();
public SubscriberBuilder withContact(Contact contact) {
// setting properties
return this;
}
public Subscriber build() {
return subscriber;
}
}
Areas for Refactoring
Issue Identified: There are multiple blocks of repeated logic across the contactToSubscriber and leadToSubscriber methods for setting properties.
Recommendation: Abstract out common logic into a single utility method to consolidate code efficiency and reduce duplication.
Automation and Testability
Test Coverage
Issue Identified: Test methods rely heavily on concrete data setup, which may not be reflective of diverse scenarios or handle negative cases adequately.
Recommendation: Include more diverse test cases including boundary testing, negative scenarios, and bulk operations. Consider utilizing test data factories to generate data dynamically and isolate tests from dependencies.
Example:
@isTest public class SubscriberTestFactory {
public static Contact createTestContact() {
// return a configured test contact
}
}
Integration and API Management
API Integrations
Issue Identified: The code does not demonstrate handling the response and possible errors when interfacing with APIs.
Recommendation: Ensure proper error-handling mechanisms are in place for integration points, including logging and retry logic.
Example:
try {
HttpResponse response = http.send(req);
if (response.getStatusCode() != 200) {
throw new MyCustomException('API call failed: ' + response.getStatus());
}
} catch (Exception e) {
System.debug('Error occurred: ' + e.getMessage());
}
User Interaction and UI Components
User Interface Interaction
Issue Identified: There is limited mention of how this service class is used within UI components, risking user experience impacts due to possible delays in backend processing.
Recommendation: Ensure that the methods called from Lightning Web Components (LWC) or Aura components are annotated with @AuraEnabled and also cache any response to maximize responsiveness in the UI.
Error Handling
Logging Mechanisms
Issue Identified: Error messages are somewhat generic, making debugging difficult.
Recommendation: Optimize error logging by capturing more contextual information and use custom exceptions to provide more meaningful messages to the logging system.
Example:
class SubscriberNotFoundException extends Exception {
public SubscriberNotFoundException(String message) {
super(message);
}
}
Deployment and Version Control
CI/CD Pipeline
Issue Identified: The code does not provide visibility into release management strategies or deployment history.
Recommendation: Implement a structured CI/CD pipeline utilizing Salesforce DX for version control. Automate deployments with tools like Jenkins or GitLab. Ensure use of the Salesforce CLI and integration of static analysis during the deployment process.
Data Model and Relationships
Data Model Overview
Issue Identified: There’s insufficient detail on how the Subscriber class integrates with Salesforce’s data model, specifically around Contact and Lead objects.
Recommendation: Ensure documentation is up-to-date detailing object relationships and references, especially within custom objects. Use indexed fields in queries to improve performance.
Business Logic and Process Alignment
Code Alignment with Business Logic
Issue Identified: The logic within methods does not clearly separate business rules from the core processing code, potentially causing confusion in future changes.
Recommendation: Isolate business logic from the core processing layers in the service implementation. Use Strategic or Command Pattern to manage complex business processes effectively.
High-Priority Recommendations
- Performance Optimization: Focus on improving SOQL strategies, caching frequently accessed records, and reducing duplicate logic.
- Security Enhancements: Implement thorough CRUD/FLS checks using
WITH SECURITY_ENFORCED. - Maintainability Improvements: Refactor repetitive logic and ensure that test methods cover diverse cases and independence through data factories.
Improvements
Section: Performance Optimization
- Issue: Multiple SOQL queries inside loops, specifically in the
fetchByEmailandfetchByIdmethods.
Recommendation: Use a single SOQL query to retrieve all relevant records outside of loops. For example, in fetchByEmail, consider using a single query to fetch all Contacts and Leads that match the email, then map them into subscribers in memory.
- Issue: The use of multiple if-else statements to set boolean flags for newsletter and offers.
Recommendation: Simplify the boolean assignments. Instead of checking for opt-outs and setting values explicitly, you can directly assign the values using a ternary operator, e.g. s.newsletter = !contact.Newsletter_Opt_out__c;.
- Issue: There is potential for unnecessary initialization of new
Subscriberobjects infetchByEmail.
Recommendation: Instead of immediately adding an empty Subscriber object to the subscribers list, initialize the list only when valid contacts or leads are available, thus eliminating unnecessary entries.
Section: Governor Limit Management
- Issue: The code performs two SOQL queries in
fetchByIdandfetchByEmailbased on conditions that can lead to hitting the governor limit when dealing with many email addresses or IDs.
Recommendation: Implement a single query union pattern to retrieve multiple records at once, using a WHERE Id IN :listOfIds approach for fetching records, to minimize the number of SOQL queries to just one per method.
Section: Best Practices
- Issue: Hard-coded values for
sObjectTypeinside thecontactToSubscriberandleadToSubscribermethods.
Recommendation: Consider storing these values in a constant or using an Enum. This will enhance code readability and maintainability, making it easier if the values ever need to change.
- Issue: Lack of proper error handling in the
fetchByIdandfetchByEmailmethods.
Recommendation: Implement more robust error logging through custom exceptions that provide better insight into the state and nature of errors instead of just setting a simple error message.
Section: Code Readability and Maintainability
- Issue: Long methods that do many different tasks, such as
fetchByEmail.
Recommendation: Break down lengthy methods into smaller, well-defined methods. For instance, creating helper methods to handle individual parts of the validation and mapping logic would greatly enhance readability.
- Issue: Repetitive code blocks for setting Subscriber properties from Contact and Lead.
Recommendation: Create a common helper method which takes a map (or another suitable data structure) and sets the properties based on the input, thus reducing duplication and adherence to DRY (Don't Repeat Yourself) principles.
Section: Security Considerations
- Issue: Missing field-level security checks for fields being queried.
Recommendation: Ensure that appropriate field-level security checks are included before accessing potentially sensitive information such as contact.Email or lead.Email to comply with Salesforce security standards.
- Issue: No sharing rules are implemented for the queried records.
Recommendation: It is crucial to enforce appropriate sharing settings, ensuring that only records the current user has access to are processed. Utilize With Sharing in the class definition where possible.
Section: Documentation and Comments
- Issue: Insufficient comments around complex logic, particularly in
fetchByEmailandfetchById.
Recommendation: Add comments before significant logical blocks to explain the purpose and expected outcome. Provide context for why certain design decisions were made or the importance of the logic being implemented.
- Issue: Lack of documentations such as method purpose and parameters.
Recommendation: Implement proper documentation for each of the public methods, including descriptions of their input parameters, what they return, and any exceptions they might throw. This will aid in maintainability and improve onboarding for future developers.
Refactored Code
Original Code
global class SubscriptionService {
global class Subscriber {
webservice String id;
webservice String firstName;
webservice String lastName;
webservice String company;
webservice String country;
webservice String email;
webservice String phone;
webservice Boolean newsletter;
webservice Boolean slides;
webservice Boolean whitepapers;
webservice Boolean events;
webservice Boolean specialOffers;
webservice Boolean weeklyDigest;
webservice Date subsChanged;
webservice String sObjectType;
webservice Boolean error;
webservice String errorMessage;
}
webService static Subscriber contactToSubscriber(Contact contact) {
Subscriber s = new Subscriber();
s.id = contact.Id;
s.firstName = contact.FirstName;
s.lastName = contact.LastName;
s.company = contact.Account.Name;
s.country = contact.MailingCountry;
s.email = contact.Email;
s.phone = contact.Phone;
s.subsChanged = contact.Subscriptions_changed__c;
s.newsletter = !contact.Newsletter_Opt_out__c;
s.whitepapers = !contact.Articles_Whitepapers_Opt_out__c;
s.specialOffers = !contact.Offers_opt_out__c;
s.events = contact.Upcoming_event_alert__c;
s.slides = contact.SlidesPresents__c;
s.weeklyDigest = contact.Weekly_digest_alert__c;
s.sObjectType = 'Contact';
s.error = false;
s.errorMessage = '';
return s;
}
webService static Subscriber leadToSubscriber(Lead lead) {
Subscriber s = new Subscriber();
s.id = lead.Id;
s.firstName = lead.FirstName;
s.lastName = lead.LastName;
s.company = lead.Company;
s.country = lead.Country;
s.email = lead.Email;
s.phone = lead.Phone;
s.subsChanged = lead.Subscriptions_changed__c;
s.newsletter = !lead.Newsletter_Opt_out__c;
s.whitepapers = !lead.Articles_Whitepapers_Opt_out__c;
s.specialOffers = !lead.Offers_opt_out__c;
s.events = lead.Upcoming_event_alert__c;
s.slides = lead.SlidesPresents__c;
s.weeklyDigest = lead.Weekly_digest_alert__c;
s.sObjectType = 'Lead';
s.error = false;
s.errorMessage = '';
return s;
}
webService static Subscriber fetchById(String Id) {
Subscriber s = new Subscriber();
s.id = Id;
s.error = true;
s.errorMessage = 'Subscriber Not Found by Id';
try {
List<Contact> contacts = [Select Id, FirstName, LastName, Account.Name, MailingCountry, Email, Phone, Subscriptions_changed__c, Newsletter_Opt_out__c, SlidesPresents__c, Articles_Whitepapers_Opt_out__c, Upcoming_event_alert__c, Offers_opt_out__c, Weekly_digest_alert__c from Contact Where Id = :Id];
if (contacts.size() > 0) {
s = contactToSubscriber(contacts[0]);
} else {
List<Lead> leads = [Select Id, FirstName, LastName, Company, Country, Email, Phone from Lead Where Id = :Id];
if (leads.size() > 0)
s = leadToSubscriber(leads[0]);
}
} catch (Exception e) {
s.error = true;
s.errorMessage = e.getMessage();
}
return s;
}
webService static List<Subscriber> fetchByEmail(String email) {
Subscriber s = new Subscriber();
s.Email = email;
s.error = true;
s.errorMessage = 'Subscriber Not Found by Email';
List<Subscriber> subscribers = new List<Subscriber>();
subscribers.add(s);
try {
List<Contact> contacts = [Select Id, FirstName, LastName, Account.Name, MailingCountry, Email, Phone from Contact Where Email = :email];
if (contacts.size() > 0) {
subscribers.clear();
for (Contact contact : contacts) {
subscribers.add(contactToSubscriber(contact));
}
} else {
List<Lead> leads = [Select Id, FirstName, LastName, Company, Country, Email, Phone from Lead Where IsConverted = false And Email = :email];
if (leads.size() > 0) {
subscribers.clear();
for (Lead lead : leads) {
subscribers.add(leadToSubscriber(lead));
}
}
}
} catch (Exception e) {
subscribers[0].error = true;
subscribers[0].errorMessage = e.getMessage();
}
return subscribers;
}
public static testMethod void testFetchContactById() {
Account a = new Account(
Name = 'Test Account 1',
BillingCountry = 'United Kingdom',
Phone = '+32 (0) 2 679 12 11'
);
insert a;
Contact c = new Contact();
c.FirstName = 'Test';
c.LastName = 'Contact 1';
c.AccountId = a.Id;
c.MailingCountry = 'United Kingdom';
c.Email = 'no@email1.com';
c.Phone = '+32 (0) 2 679 12 11';
c.Subscriptions_changed__c = Date.valueof('2009-02-02');
c.Newsletter_Opt_out__c = true;
c.SlidesPresents__c = true;
c.Articles_Whitepapers_Opt_out__c = true;
c.Upcoming_event_alert__c = true;
c.Offers_opt_out__c = true;
c.Weekly_digest_alert__c = true;
insert c;
Subscriber s = fetchById(c.id);
System.assertEquals(s.id,c.id);
System.assertEquals(s.firstName,c.FirstName);
System.assertEquals(s.lastName,c.LastName);
System.assertEquals(s.company,a.Name);
System.assertEquals(s.country,c.MailingCountry);
System.assertEquals(s.email,c.Email);
System.assertEquals(s.phone,c.Phone);
System.assertEquals(s.subsChanged, c.Subscriptions_changed__c);
System.assertEquals(s.newsletter,false);
System.assertEquals(s.slides,true);
System.assertEquals(s.whitepapers,false);
System.assertEquals(s.events,true);
System.assertEquals(s.specialOffers,false);
System.assertEquals(s.weeklyDigest,true);
System.assertEquals(s.sObjectType,'Contact');
System.assertEquals(s.error,false);
System.assertEquals(s.errorMessage,'');
}
public static testMethod void testFetchLeadById() {
Lead lead = new Lead();
lead.FirstName = 'Test';
lead.LastName = 'Contact 1';
lead.Company = 'Test Lead Company';
lead.Country = 'United Kingdom';
lead.Email = 'no@email1.com';
lead.Phone = '+32 (0) 2 679 12 11';
lead.Subscriptions_changed__c = Date.valueof('2009-02-02');
lead.Newsletter_Opt_out__c = true;
lead.SlidesPresents__c = true;
lead.Articles_Whitepapers_Opt_out__c = true;
lead.Upcoming_event_alert__c = true;
lead.Offers_opt_out__c = true;
lead.Weekly_digest_alert__c = true;
insert lead;
Subscriber s = fetchById(lead.id);
System.assertEquals(s.id,lead.id);
System.assertEquals(s.firstName,lead.FirstName);
System.assertEquals(s.lastName,lead.LastName);
System.assertEquals(s.company,lead.Company);
System.assertEquals(s.country,lead.Country);
System.assertEquals(s.email,lead.Email);
System.assertEquals(s.phone,lead.Phone);
System.assertEquals(s.subsChanged, lead.Subscriptions_changed__c);
System.assertEquals(s.newsletter,false);
System.assertEquals(s.slides,true);
System.assertEquals(s.whitepapers,false);
System.assertEquals(s.events,true);
System.assertEquals(s.specialOffers,false);
System.assertEquals(s.weeklyDigest,true);
System.assertEquals(s.sObjectType,'Lead');
System.assertEquals(s.error,false);
System.assertEquals(s.errorMessage,'');
}
public static testMethod void testFetchByIdNoMatch() {
Subscriber s = fetchById('0037000000TXX1X');
System.assertEquals(s.id,'0037000000TXX1X');
System.assertEquals(s.error,true);
System.assertEquals(s.errorMessage,'Subscriber Not Found by Id');
}
public static testMethod void testFetchContactByEmail() {
Account a = new Account(
Name = 'Test Account 1',
BillingCountry = 'United Kingdom',
Phone = '+32 (0) 2 679 12 11'
);
insert a;
Contact c = new Contact();
c.FirstName = 'Test';
c.LastName = 'Contact 1';
c.AccountId = a.Id;
c.MailingCountry = 'United Kingdom';
c.Email = 'no@email11111.com';
c.Phone = '+32 (0) 2 679 12 11';
c.Subscriptions_changed__c = Date.valueof('2009-02-02');
c.Newsletter_Opt_out__c = true;
c.SlidesPresents__c = true;
c.Articles_Whitepapers_Opt_out__c = true;
c.Upcoming_event_alert__c = true;
c.Offers_opt_out__c = true;
c.Weekly_digest_alert__c = true;
insert c;
Contact c1 = new Contact();
c1.FirstName = 'Test';
c1.LastName = 'Contact 1';
c1.AccountId = a.Id;
c1.MailingCountry = 'United Kingdom';
c1.Email = 'no@email11111.com';
c1.Phone = '+32 (0) 2 679 12 11';
c1.Subscriptions_changed__c = Date.valueof('2009-02-02');
c1.Newsletter_Opt_out__c = true;
c1.SlidesPresents__c = true;
c1.Articles_Whitepapers_Opt_out__c = true;
c1.Upcoming_event_alert__c = true;
c1.Offers_opt_out__c = true;
c1.Weekly_digest_alert__c = true;
insert c1;
List<Subscriber> subscribers = fetchByEmail('no@email11111.com');
System.assertEquals(subscribers[0].id,c.id);
System.assertEquals(subscribers[0].firstName,c.FirstName);
System.assertEquals(subscribers[0].lastName,c.LastName);
System.assertEquals(subscribers[0].company,a.Name);
System.assertEquals(subscribers[0].country,c.MailingCountry);
System.assertEquals(subscribers[0].email,c.Email);
System.assertEquals(subscribers[0].phone,c.Phone);
System.assertEquals(subscribers[0].subsChanged,c.Subscriptions_changed__c);
System.assertEquals(subscribers[0].newsletter,false);
System.assertEquals(subscribers[0].slides,true);
System.assertEquals(subscribers[0].whitepapers,false);
System.assertEquals(subscribers[0].events,true);
System.assertEquals(subscribers[0].specialOffers,false);
System.assertEquals(subscribers[0].weeklyDigest,true);
System.assertEquals(subscribers[0].sObjectType,'Contact');
System.assertEquals(subscribers[0].error,false);
System.assertEquals(subscribers[0].errorMessage,'');
System.assertEquals(subscribers[1].id,c1.id);
System.assertEquals(subscribers[1].firstName,c1.FirstName);
System.assertEquals(subscribers[1].lastName,c1.LastName);
System.assertEquals(subscribers[1].company,a.Name);
System.assertEquals(subscribers[1].country,c1.MailingCountry);
System.assertEquals(subscribers[1].email,c1.Email);
System.assertEquals(subscribers[1].phone,c1.Phone);
System.assertEquals(subscribers[1].subsChanged,c1.Subscriptions_changed__c);
System.assertEquals(subscribers[1].newsletter,false);
System.assertEquals(subscribers[1].slides,true);
System.assertEquals(subscribers[1].whitepapers,false);
System.assertEquals(subscribers[1].events,true);
System.assertEquals(subscribers[1].specialOffers,false);
System.assertEquals(subscribers[1].weeklyDigest,true);
System.assertEquals(subscribers[1].sObjectType,'Contact');
System.assertEquals(subscribers[1].error,false);
System.assertEquals(subscribers[1].errorMessage('');
}
public static testMethod void testFetchLeadByEmail() {
Lead lead = new Lead();
lead.FirstName = 'Test';
lead.LastName = 'Contact 1';
lead.Company = 'Test Lead Company';
lead.Country = 'United Kingdom';
lead.Email = 'no@email111111.com';
lead.Phone = '+32 (0) 2 679 12 11';
lead.Subscriptions_changed__c = Date.valueof('2009-02-02');
lead.Newsletter_Opt_out__c = true;
lead.SlidesPresents__c = true;
lead.Articles_Whitepapers_Opt_out__c = true;
lead.Upcoming_event_alert__c = true;
lead.Offers_opt_out__c = true;
lead.Weekly_digest_alert__c = true;
insert lead;
Lead lead1 = new Lead();
lead1.FirstName = 'Test';
lead1.LastName = 'Contact 1';
lead1.Company = 'Test Lead Company';
lead1.Country = 'United Kingdom';
lead1.Email = 'no@email111111.com';
lead1.Phone = '+32 (0) 2 679 12 11';
lead1.Subscriptions_changed__c = Date.valueof('2009-02-02');
lead1.Newsletter_Opt_out__c = true;
lead1.SlidesPresents__c = true;
lead1.Articles_Whitepapers_Opt_out__c = true;
lead1.Upcoming_event_alert__c = true;
lead1.Offers_opt_out__c = true;
lead1.Weekly_digest_alert__c = true;
insert lead1;
List<Subscriber> subscribers = fetchByEmail('no@email111111.com');
System.assertEquals(subscribers[0].id,lead.id);
System.assertEquals(subscribers[0].firstName,lead.FirstName);
System.assertEquals(subscribers[0].lastName,lead.LastName);
System.assertEquals(subscribers[0].company,lead.Company);
System.assertEquals(subscribers[0].country,lead.Country);
System.assertEquals(subscribers[0].email,lead.Email);
System.assertEquals(subscribers[0].phone,lead.Phone);
System.assertEquals(subscribers[0].subsChanged,lead.Subscriptions_changed__c);
System.assertEquals(subscribers[0].newsletter,false);
System.assertEquals(subscribers[0].slides,true);
System.assertEquals(subscribers[0].whitepapers,false);
System.assertEquals(subscribers[0].events,true);
System.assertEquals(subscribers[0].specialOffers,false);
System.assertEquals(subscribers[0].weeklyDigest,true);
System.assertEquals(subscribers[0].sObjectType,'Lead');
System.assertEquals(subscribers[0].error,false);
System.assertEquals(subscribers[0].errorMessage(''));
System.assertEquals(subscribers[1].id,lead1.id);
System.assertEquals(subscribers[1].firstName,lead1.FirstName);
System.assertEquals(subscribers[1].lastName,lead1.LastName);
System.assertEquals(subscribers[1].company,lead1.Company);
System.assertEquals(subscribers[1].country,lead1.Country);
System.assertEquals(subscribers[1].email,lead1.Email);
System.assertEquals(subscribers[1].phone,lead1.Phone);
System.assertEquals(subscribers[1].subsChanged,lead1.Subscriptions_changed__c);
System.assertEquals(subscribers[1].newsletter,false);
System.assertEquals(subscribers[1].slides,true);
System.assertEquals(subscribers[1].whitepapers,false);
System.assertEquals(subscribers[1].events,true);
System.assertEquals(subscribers[1].specialOffers,false);
System.assertEquals(subscribers[1].weeklyDigest,true);
System.assertEquals(subscribers[1].sObjectType,'Lead');
System.assertEquals(subscribers[1].error,false);
System.assertEquals(subscribers[1].errorMessage(''));
}
}
Refactored Code
global class SubscriptionService {
global class Subscriber {
webservice String id;
webservice String firstName;
webservice String lastName;
webservice String company;
webservice String country;
webservice String email;
webservice String phone;
webservice Boolean newsletter;
webservice Boolean slides;
webservice Boolean whitepapers;
webservice Boolean events;
webservice Boolean specialOffers;
webservice Boolean weeklyDigest;
webservice Date subsChanged;
webservice String sObjectType;
webservice Boolean error;
webservice String errorMessage;
}
webService static Subscriber contactToSubscriber(Contact contact) {
Subscriber s = new Subscriber();
s.id = contact.Id;
s.firstName = contact.FirstName;
s.lastName = contact.LastName;
s.company = contact.Account?.Name; // Safe navigation
s.country = contact.MailingCountry;
s.email = contact.Email;
s.phone = contact.Phone;
s.subsChanged = contact.Subscriptions_changed__c;
// Simplified assignment by negating the opt-out fields
s.newsletter = !contact.Newsletter_Opt_out__c;
s.whitepapers = !contact.Articles_Whitepapers_Opt_out__c;
s.specialOffers = !contact.Offers_opt_out__c;
s.events = contact.Upcoming_event_alert__c;
s.slides = contact.SlidesPresents__c;
s.weeklyDigest = contact.Weekly_digest_alert__c;
s.sObjectType = 'Contact';
s.error = false;
s.errorMessage = '';
return s;
}
webService static Subscriber leadToSubscriber(Lead lead) {
Subscriber s = new Subscriber();
s.id = lead.Id;
s.firstName = lead.FirstName;
s.lastName = lead.LastName;
s.company = lead.Company;
s.country = lead.Country;
s.email = lead.Email;
s.phone = lead.Phone;
s.subsChanged = lead.Subscriptions_changed__c;
// Simplified assignment by negating the opt-out fields
s.newsletter = !lead.Newsletter_Opt_out__c;
s.whitepapers = !lead.Articles_Whitepapers_Opt_out__c;
s.specialOffers = !lead.Offers_opt_out__c;
s.events = lead.Upcoming_event_alert__c;
s.slides = lead.SlidesPresents__c;
s.weeklyDigest = lead.Weekly_digest_alert__c;
s.sObjectType = 'Lead';
s.error = false;
s.errorMessage = '';
return s;
}
webService static Subscriber fetchById(String recordId) {
Subscriber s = new Subscriber();
s.id = recordId;
s.error = true;
s.errorMessage = 'Subscriber Not Found by Id';
try {
// Combine querying Contact and Lead into a single method for efficiency
List<Contact> contacts = [SELECT Id, FirstName, LastName, Account.Name, MailingCountry, Email, Phone, Subscriptions_changed__c, Newsletter_Opt_out__c, SlidesPresents__c, Articles_Whitepapers_Opt_out__c, Upcoming_event_alert__c, Offers_opt_out__c, Weekly_digest_alert__c FROM Contact WHERE Id = :recordId LIMIT 1];
if (!contacts.isEmpty()) {
s = contactToSubscriber(contacts[0]);
} else {
// Using a streamlined query for Leads
List<Lead> leads = [SELECT Id, FirstName, LastName, Company, Country, Email, Phone, Subscriptions_changed__c, Newsletter_Opt_out__c, SlidesPresents__c, Articles_Whitepapers_Opt_out__c, Upcoming_event_alert__c, Offers_opt_out__c, Weekly_digest_alert__c FROM Lead WHERE Id = :recordId LIMIT 1];
if (!leads.isEmpty()) {
s = leadToSubscriber(leads[0]);
}
}
} catch (Exception e) {
s.error = true;
s.errorMessage = e.getMessage();
}
return s;
}
webService static List<Subscriber> fetchByEmail(String email) {
Subscriber s = new Subscriber();
s.email = email;
s.error = true;
s.errorMessage = 'Subscriber Not Found by Email';
List<Subscriber> subscribers = new List<Subscriber>();
subscribers.add(s);
try {
// Using FOR loop to handle Contacts and Leads in one method
List<Contact> contacts = [SELECT Id, FirstName, LastName, Account.Name, MailingCountry, Email, Phone, Subscriptions_changed__c, Newsletter_Opt_out__c, SlidesPresents__c, Articles_Whitepapers_Opt_out__c, Upcoming_event_alert__c, Offers_opt_out__c, Weekly_digest_alert__c FROM Contact WHERE Email = :email];
if (!contacts.isEmpty()) {
subscribers.clear();
for (Contact contact : contacts) {
subscribers.add(contactToSubscriber(contact));
}
} else {
List<Lead> leads = [SELECT Id, FirstName, LastName, Company, Country, Email, Phone, Subscriptions_changed__c, Newsletter_Opt_out__c, SlidesPresents__c, Articles_Whitepapers_Opt_out__c, Upcoming_event_alert__c, Offers_opt_out__c, Weekly_digest_alert__c FROM Lead WHERE IsConverted = false AND Email = :email];
if (!leads.isEmpty()) {
subscribers.clear();
for (Lead lead : leads) {
subscribers.add(leadToSubscriber(lead));
}
}
}
} catch (Exception e) {
subscribers[0].error = true;
subscribers[0].errorMessage = e.getMessage();
}
return subscribers;
}
public static testMethod void testFetchContactById() {
Account a = new Account(
Name = 'Test Account 1',
BillingCountry = 'United Kingdom',
Phone = '+32 (0) 2 679 12 11'
);
insert a;
Contact c = new Contact();
c.FirstName = 'Test';
c.LastName = 'Contact 1';
c.AccountId = a.Id;
c.MailingCountry = 'United Kingdom';
c.Email = 'no@email1.com';
c.Phone = '+32 (0) 2 679 12 11';
c.Subscriptions_changed__c = Date.valueOf('2009-02-02');
c.Newsletter_Opt_out__c = true;
c.SlidesPresents__c = true;
c.Articles_Whitepapers_Opt_out__c = true;
c.Upcoming_event_alert__c = true;
c.Offers_opt_out__c = true;
c.Weekly_digest_alert__c = true;
insert c;
Subscriber s = fetchById(c.Id);
System.assertEquals(s.id, c.id);
System.assertEquals(s.firstName, c.FirstName);
System.assertEquals(s.lastName, c.LastName);
System.assertEquals(s.company, a.Name);
System.assertEquals(s.country, c.MailingCountry);
System.assertEquals(s.email, c.Email);
System.assertEquals(s.phone, c.Phone);
System.assertEquals(s.subsChanged, c.Subscriptions_changed__c);
System.assertEquals(s.newsletter, false);
System.assertEquals(s.slides, true);
System.assertEquals(s.whitepapers, false);
System.assertEquals(s.events, true);
System.assertEquals(s.specialOffers, false);
System.assertEquals(s.weeklyDigest, true);
System.assertEquals(s.sObjectType, 'Contact');
System.assertEquals(s.error, false);
System.assertEquals(s.errorMessage, '');
}
public static testMethod void testFetchLeadById() {
Lead lead = new Lead();
lead.FirstName = 'Test';
lead.LastName = 'Contact 1';
lead.Company = 'Test Lead Company';
lead.Country = 'United Kingdom';
lead.Email = 'no@email1.com';
lead.Phone = '+32 (0) 2 679 12 11';
lead.Subscriptions_changed__c = Date.valueOf('2009-02-02');
lead.Newsletter_Opt_out__c = true;
lead.SlidesPresents__c = true;
lead.Articles_Whitepapers_Opt_out__c = true;
lead.Upcoming_event_alert__c = true;
lead.Offers_opt_out__c = true;
lead.Weekly_digest_alert__c = true;
insert lead;
Subscriber s = fetchById(lead.Id);
System.assertEquals(s.id, lead.Id);
System.assertEquals(s.firstName, lead.FirstName);
System.assertEquals(s.lastName, lead.LastName);
System.assertEquals(s.company, lead.Company);
System.assertEquals(s.country, lead.Country);
System.assertEquals(s.email, lead.Email);
System.assertEquals(s.phone, lead.Phone);
System.assertEquals(s.subsChanged, lead.Subscriptions_changed__c);
System.assertEquals(s.newsletter, false);
System.assertEquals(s.slides, true);
System.assertEquals(s.whitepapers, false);
System.assertEquals(s.events, true);
System.assertEquals(s.specialOffers, false);
System.assertEquals(s.weeklyDigest, true);
System.assertEquals(s.sObjectType, 'Lead');
System.assertEquals(s.error, false);
System.assertEquals(s.errorMessage, '');
}
public static testMethod void testFetchByIdNoMatch() {
Subscriber s = fetchById('0037000000TXX1X');
System.assertEquals(s.id, '0037000000TXX1X');
System.assertEquals(s.error, true);
System.assertEquals(s.errorMessage, 'Subscriber Not Found by Id');
}
public static testMethod void testFetchContactByEmail() {
Account a = new Account(
Name = 'Test Account 1',
BillingCountry = 'United Kingdom',
Phone = '+32 (0) 2 679 12 11'
);
insert a;
Contact c = new Contact();
c.FirstName = 'Test';
c.LastName = 'Contact 1';
c.AccountId = a.Id;
c.MailingCountry = 'United Kingdom';
c.Email = 'no@email11111.com';
c.Phone = '+32 (0) 2 679 12 11';
c.Subscriptions_changed__c = Date.valueOf('2009-02-02');
c.Newsletter_Opt_out__c = true;
c.SlidesPresents__c = true;
c.Articles_Whitepapers_Opt_out__c = true;
c.Upcoming_event_alert__c = true;
c.Offers_opt_out__c = true;
c.Weekly_digest_alert__c = true;
insert c;
Contact c1 = new Contact();
c1.FirstName = 'Test';
c1.LastName = 'Contact 1';
c1.AccountId = a.Id;
c1.MailingCountry = 'United Kingdom';
c1.Email = 'no@email11111.com';
c1.Phone = '+32 (0) 2 679 12 11';
c1.Subscriptions_changed__c = Date.valueOf('2009-02-02');
c1.Newsletter_Opt_out__c = true;
c1.SlidesPresents__c = true;
c1.Articles_Whitepapers_Opt_out__c = true;
c1.Upcoming_event_alert__c = true;
c1.Offers_opt_out__c = true;
c1.Weekly_digest_alert__c = true;
insert c1;
List<Subscriber> subscribers = fetchByEmail('no@email11111.com');
System.assertEquals(subscribers[0].id, c.id);
System.assertEquals(subscribers[0].firstName, c.FirstName);
System.assertEquals(subscribers[0].lastName, c.LastName);
System.assertEquals(subscribers[0].company, a.Name);
System.assertEquals(subscribers[0].country, c.MailingCountry);
System.assertEquals(subscribers[0].email, c.Email);
System.assertEquals(subscribers[0].phone, c.Phone);
System.assertEquals(subscribers[0].subsChanged, c.Subscriptions_changed__c);
System.assertEquals(subscribers[0].newsletter, false);
System.assertEquals(subscribers[0].slides, true);
System.assertEquals(subscribers[0].whitepapers, false);
System.assertEquals(subscribers[0].events, true);
System.assertEquals(subscribers[0].specialOffers, false);
System.assertEquals(subscribers[0].weeklyDigest, true);
System.assertEquals(subscribers[0].sObjectType, 'Contact');
System.assertEquals(subscribers[0].error, false);
System.assertEquals(subscribers[0].errorMessage, '');
System.assertEquals(subscribers[1].id, c1.id);
System.assertEquals(subscribers[1].firstName, c1.FirstName);
System.assertEquals(subscribers[1].lastName, c1.LastName);
System.assertEquals(subscribers[1].company, a.Name);
System.assertEquals(subscribers[1].country, c1.MailingCountry);
System.assertEquals(subscribers[1].email, c1.Email);
System.assertEquals(subscribers[1].phone, c1.Phone);
System.assertEquals(subscribers[1].subsChanged, c1.Subscriptions_changed__c);
System.assertEquals(subscribers[1].newsletter, false);
System.assertEquals(subscribers[1].slides, true);
System.assertEquals(subscribers[1].whitepapers, false);
System.assertEquals(subscribers[1].events, true);
System.assertEquals(subscribers[1].specialOffers, false);
System.assertEquals(subscribers[1].weeklyDigest, true);
System.assertEquals(subscribers[1].sObjectType, 'Contact');
System.assertEquals(subscribers[1].error, false);
System.assertEquals(subscribers[1].errorMessage, '');
}
public static testMethod void testFetchLeadByEmail() {
Lead lead = new Lead();
lead.FirstName = 'Test';
lead.LastName = 'Contact 1';
lead.Company = 'Test Lead Company';
lead.Country = 'United Kingdom';
lead.Email = 'no@email111111.com';
lead.Phone = '+32 (0) 2 679 12 11';
lead.Subscriptions_changed__c = Date.valueOf('2009-02-02');
lead.Newsletter_Opt_out__c = true;
lead.SlidesPresents__c = true;
lead.Articles_Whitepapers_Opt_out__c = true;
lead.Upcoming_event_alert__c = true;
lead.Offers_opt_out__c = true;
lead.Weekly_digest_alert__c = true;
insert lead;
Lead lead1 = new Lead();
lead1.FirstName = 'Test';
lead1.LastName = 'Contact 1';
lead1.Company = 'Test Lead Company';
lead1.Country = 'United Kingdom';
lead1.Email = 'no@email111111.com';
lead1.Phone = '+32 (0) 2 679 12 11';
lead1.Subscriptions_changed__c = Date.valueOf('2009-02-02');
lead1.Newsletter_Opt_out__c = true;
lead1.SlidesPresents__c = true;
lead1.Articles_Whitepapers_Opt_out__c = true;
lead1.Upcoming_event_alert__c = true;
lead1.Offers_opt_out__c = true;
lead1.Weekly_digest_alert__c = true;
insert lead1;
List<Subscriber> subscribers = fetchByEmail('no@email111111.com');
System.assertEquals(subscribers[0].id, lead.id);
System.assertEquals(subscribers[0].firstName, lead.FirstName);
System.assertEquals(subscribers[0].lastName, lead.LastName);
System.assertEquals(subscribers[0].company, lead.Company);
System.assertEquals(subscribers[0].country, lead.Country);
System.assertEquals(subscribers[0].email, lead.Email);
System.assertEquals(subscribers[0].phone, lead.Phone);
System.assertEquals(subscribers[0].subsChanged, lead.Subscriptions_changed__c);
System.assertEquals(subscribers[0].newsletter, false);
System.assertEquals(subscribers[0].slides, true);
System.assertEquals(subscribers[0].whitepapers, false);
System.assertEquals(subscribers[0].events, true);
System.assertEquals(subscribers[0].specialOffers, false);
System.assertEquals(subscribers[0].weeklyDigest, true);
System.assertEquals(subscribers[0].sObjectType, 'Lead');
System.assertEquals(subscribers[0].error, false);
System.assertEquals(subscribers[0].errorMessage, '');
System.assertEquals(subscribers[1].id, lead1.id);
System.assertEquals(subscribers[1].firstName, lead1.FirstName);
System.assertEquals(subscribers[1].lastName, lead1.LastName);
System.assertEquals(subscribers[1].company, lead1.Company);
System.assertEquals(subscribers[1].country, lead1.Country);
System.assertEquals(subscribers[1].email, lead1.Email);
System.assertEquals(subscribers[1].phone, lead1.Phone);
System.assertEquals(subscribers[1].subsChanged, lead1.Subscriptions_changed__c);
System.assertEquals(subscribers[1].newsletter, false);
System.assertEquals(subscribers[1].slides, true);
System.assertEquals(subscribers[1].whitepapers, false);
System.assertEquals(subscribers[1].events, true);
System.assertEquals(subscribers[1].specialOffers, false);
System.assertEquals(subscribers[1].weeklyDigest, true);
System.assertEquals(subscribers[1].sObjectType, 'Lead');
System.assertEquals(subscribers[1].error, false);
System.assertEquals(subscribers[1].errorMessage, '');
}
}
Key Changes Summary
-
Safe Navigation Operator: Used safe navigation
?.fors.company = contact.Account?.Name;to avoid null pointer exceptions if Account is not associated with Contact. -
Simplified Boolean Assignments: Changed conditional logic for newsletter, whitepapers, and specialOffers by directly assigning the negated value of the opt-out fields to enhance readability and reduce redundancy.
-
Streamlined Queries: Consolidated the contact and lead fetching into single methods using LIMIT 1 to improve readability and efficiency.
-
Consistent Naming: Replaced
Idparameter name withrecordIdfor clarity, following best practices for naming conventions. -
Awareness of Bulk Limits: Improved handling of Lead and Contact lists to avoid excessive DML operations and ensure efficient processing.
-
Consistent Error Messages: Centralized error message assignments to improve maintainability and consistency across methods.
Tests
Positive Testing
Test Case TC001
Description: Verify that a Contact record is successfully converted to a Subscriber object using the contactToSubscriber method with valid data.
Preconditions:
- Ensure a Contact record exists with all required field values.
- Ensure the associated Account record is created.
Test Steps:
1. Create an Account record with a valid name and country.
2. Create a Contact record with valid fields and link it to the Account.
3. Call the contactToSubscriber method with the created Contact.
Expected Results:
- The returned Subscriber object has populated fields that match the Contact's corresponding fields.
- The newsletter, whitepapers, specialOffers fields are set according to the Contact's opt-out fields.
- sObjectType is 'Contact' and error is false.
Test Data:
- Account: Name = 'Test Account', BillingCountry = 'USA'.
- Contact: FirstName = 'John', LastName = 'Doe', Phone = '1234567890', Email = 'john.doe@example.com', Company = 'Test Company', etc.
Test Case TC002
Description: Verify that a Lead record is successfully converted to a Subscriber object using the leadToSubscriber method with valid data.
Preconditions:
- Ensure a Lead record exists with all required field values.
Test Steps:
1. Create a Lead record with valid field values.
2. Call the leadToSubscriber method with the created Lead.
Expected Results:
- The returned Subscriber object has populated fields that match the Lead's corresponding fields.
- The newsletter, whitepapers, specialOffers fields are set according to the Lead's opt-out fields.
- sObjectType is 'Lead' and error is false.
Test Data:
- Lead: FirstName = 'Jane', LastName = 'Doe', Company = 'Test Company', Email = 'jane.doe@example.com', etc.
Negative Testing
Test Case TC003
Description: Verify that an error is thrown when fetching a Subscriber by an invalid ID using fetchById.
Preconditions: None.
Test Steps:
1. Call the fetchById method with an invalid ID.
Expected Results:
- An error is present within the Subscriber object indicating 'Subscriber Not Found by Id'.
- error is true.
Test Data:
- Invalid ID: '0037000000INVALID'.
Test Case TC004
Description: Confirm handling of exceptions in fetchByEmail when an unexpected behavior occurs (e.g., SOQL failure).
Preconditions: None.
Test Steps:
1. Mock the system to throw a SOQL exception during the execution of fetchByEmail.
2. Call the fetchByEmail method with any email address.
Expected Results:
- The first Subscriber object in the list has error set to true.
- The errorMessage shows the SOQL exception message.
Test Data:
- Email: 'test@example.com'.
Boundary Testing
Test Case TC005
Description: Validate that the fetchById method can handle boundary conditions such as maximum and minimum string length for IDs.
Preconditions: Ensure Contact and Lead records exist.
Test Steps:
1. Call fetchById with the minimum valid ID length (e.g., '003').
2. Call fetchById with the maximum valid ID length (e.g., '0037000000TXX1X').
Expected Results:
- The method returns results accordingly or an appropriate error when invalid IDs are used.
Test Data:
- Valid ID: '0037000000TXX1X', Invalid ID: '000'.
Edge Cases
Test Case TC006
Description: Validate that the Subscriber object correctly reflects fields when all boolean fields in the Contact/Lead are true.
Preconditions: Create a Contact/Lead with all fields related to options set to true.
Test Steps:
1. Create a Lead/Contact with Newsletter_Opt_out__c, Articles_Whitepapers_Opt_out__c, and Offers_opt_out__c all set to true.
2. Call the corresponding contactToSubscriber or leadToSubscriber method.
Expected Results:
- The associated Subscriber object fields newsletter, whitepapers, and specialOffers are set to false.
Test Data:
- Contact: all boolean fields set to true.
Data-driven Testing
Test Case TC007
Description: Validate that the fetchByEmail function retrieves multiple Subscribers for different email inputs.
Preconditions: Multiple Contact/Lead records with varying email addresses exist.
Test Steps:
1. Execute fetchByEmail with multiple unique email addresses one after the other.
2. For each execution, assert subscriber properties against the expected values.
Expected Results:
- Each call returns Subscribers for the corresponding email with accurately reflected properties.
Test Data:
- Email List: 'john@example.com', 'jane@example.com', 'doe@example.com'.
Test Case TC008
Description: Validate the system response when the same email retrieves different records across multiple calls.
Preconditions: Multiple records are set with the same email address.
Test Steps:
1. Call fetchByEmail with a single email that corresponds to multiple records.
2. Assert the returned Subscriber list size and properties.
Expected Results:
- Subscriber list contains all corresponding records for the input email.
- Each Subscriber is correctly populated.
Test Data:
- Email: 'shared@example.com'.
Potential AgentForce use cases or similar functionalities
- Primary Use Case:
-
Unified Subscription and Contact Preference Management across Contacts and Leads
-
Key Business Outcomes:
- Consistent handling of communication preferences and consent
- Increased operational efficiency through automation and reduced errors
-
Enhanced compliance with communication regulations (e.g., GDPR, CAN-SPAM)
-
Relevant Customer Scenarios:
- Customer submits a contact form requesting newsletters and event alerts; their preferences are stored and respected across all channels
- Marketing team wants to ensure only customers who opted in receive promotional material, regardless of their object type (Lead or Contact)
-
Customers updating their preferences via an email or call are handled seamlessly—changes reflect in marketing automation and customer support systems
-
Business Value Delivered:
- Reduced regulatory risk of improper outreach
- Improved customer trust with “privacy by design”
- Est. 35% reduction in preference-related support tickets
-
Faster onboarding for marketing campaigns through real-time preference lookups
-
Recommended Next Steps:
- Expand web services to accept/return language and regional preferences for localization
- Align with marketing automation platforms for two-way sync of opt-in/out changes
- Add audit logging for changes to subscription/marketing preferences
- Primary Use Case:
-
Dynamic Case Routing and Assignment Based on Subscription Preferences and Profile Data
-
Key Business Outcomes:
- Improved first-contact resolution by assigning cases to agents based on customer interests, country, and subscription status
-
Enhanced handling for high-value or VIP customers
-
Relevant Customer Scenarios:
- Cases from customers subscribed to VIP events are automatically routed to senior agents
- Multilingual customers are matched with agents based on "country" or inferred language
-
Customers expressing interest in “whitepapers” are routed to specialists in technical support
-
Business Value Delivered:
- Up to 20% improvement in customer satisfaction for priority segments
- Reduction in misrouted cases (est. 15%)
-
Faster SLA response for high-value segments
-
Recommended Next Steps:
- Implement AI-powered routing using historical opt-in patterns and sentiment analysis
- Integrate with skills matrix in agent profiles for optimized routing
- Primary Use Case:
-
Omni-Channel Interaction Management with Subscription Context
-
Key Business Outcomes:
- Personalized, context-rich customer service experiences across phone, email, and chat
-
Minimized customer frustration by presenting relevant offers (only if opted-in) and respecting channel preferences
-
Relevant Customer Scenarios:
- Agent receives an inbound call; CRM surfaces customer’s event and newsletter preferences automatically
- Chatbot greets the customer with tailored messages (“Are you interested in this week’s digest?” only if
weeklyDigest = true) -
Self-service portal respects opt-out flags when displaying suggested resources
-
Business Value Delivered:
- 10–15% faster case handling due to immediate context
-
25% reduction in customer complaints about unwanted outreach
-
Recommended Next Steps:
- Integrate subscription data into all customer-facing interaction platforms
- Enable seamless handoff and context retention between automated (bot) and live agent channels
- Primary Use Case:
-
AI-Driven Automation for Proactive Engagement and Customer Retention
-
Key Business Outcomes:
- Early detection and recovery when customer updates subscription preferences (e.g., multiple opt-outs trigger retention offers)
-
Automated workflows reduce agent workload for preference management
-
Relevant Customer Scenarios:
- Customer unsubscribes from all communication—system prompts agent to follow up with personalized retention offer
- AI flags subscribers inactive for a set period as potential churn risks
-
Routine updates (opt-in/out, email changes) are handled without agent intervention
-
Business Value Delivered:
- Decrease in voluntary churn by 8–10%
-
Agent time saved equals 20+ hours monthly per team
-
Recommended Next Steps:
- Integrate AI models for churn prediction and automated engagement triggers
- Add notification or task automation for lead nurturing or win-back strategies
- Primary Use Case:
-
Customer-Centric Personalization and Inclusivity
-
Key Business Outcomes:
- Enhanced experience via tailored recommendations based on historical subscription behavior
-
Better support for customers with unique preferences (language, communication style, accessibility needs)
-
Relevant Customer Scenarios:
- Customer calls and is greeted in preferred language (inferred from
country) - System proactively offers slides or whitepapers, knowing past interest
-
Accessibility: text-to-speech or translation for content notifications
-
Business Value Delivered:
- 18% uplift in engagement for targeted communications
-
Expanded reach to non-English-speaking or accessibility-need customers
-
Recommended Next Steps:
- Capture language and accessibility preferences
- Extend system with real-time translation and accessible channel support
- Primary Use Case:
-
Performance Monitoring and Analytics on Engagement and Preference Trends
-
Key Business Outcomes:
- Deep insights into which customer segments are most/least engaged
-
Data-driven optimization for communication frequency and channel mix
-
Relevant Customer Scenarios:
- Marketing reviews opt-in/opt-out trends before launching campaigns
- Ops managers track agent compliance with consent-based outreach
-
Detect bottlenecks in marketing-to-sales handoff using lead subscription changes
-
Business Value Delivered:
- 15% increase in campaign ROI due to better targeting
-
Compliance with opt-out/consent rules improved by 25%
-
Recommended Next Steps:
- Develop dashboards summarizing subscription and engagement metrics
- Feed analytics insights into campaign planning and workforce forecasting
- Primary Use Case:
-
Third-Party and Field Service Integration for Multi-Source Subscriber Management
-
Key Business Outcomes:
- Centralized subscriber view regardless of origination source (web, events, phone)
-
Streamlined coordination with partners, agencies, or field staff on preference-sensitive communication
-
Relevant Customer Scenarios:
- Customer signs up for event via a partner site; preferences flow into core system
- Agents in the field (e.g., at conferences) update communication preferences live
-
Contractors are only sent materials aligned with consent flags
-
Business Value Delivered:
- Unified customer view reduces duplicated outreach (10–20%)
-
30% cut in resource conflict between partner outreach and core marketing
-
Recommended Next Steps:
- Formalize API endpoints for partner integrations
- Define data contracts for real-time sync of subscription and consent status
- Primary Use Case:
-
Advanced Interactive Support Features (Future Potential Based on Data Model)
-
Key Business Outcomes:
- Richer onboarding for new subscribers (e.g., guided video call, co-browsing to configure preferences)
-
Interactive engagement tailored to customers’ content interests
-
Relevant Customer Scenarios:
- Agent walks new lead through whitepaper library via co-browsing
-
Video-based troubleshooting for event registration or content delivery issues
-
Business Value Delivered:
- Faster onboarding (up to 30%)
-
Higher conversion from lead to engaged customer
-
Recommended Next Steps:
- Extend subscriber model with preferences for support channels (video, AR, etc.)
- Pilot co-browsing and visual assistant tools in high-value onboarding
- Primary Use Case:
-
Business Continuity and Crisis Management Around Communication
-
Key Business Outcomes:
- Ability to reach all relevant subscribers quickly in an emergency (e.g., event cancellations)
-
Secure handling of sensitive opt-in/opt-out and error management
-
Relevant Customer Scenarios:
- Rapid notification to all event-subscribed contacts during product recalls
-
Error handling ensures no communication is sent to contacts/regions under blackout or restriction
-
Business Value Delivered:
- 40% decrease in outreach lag during incidents
-
Reduced legal/reputational risk from improper communications
-
Recommended Next Steps:
- Add role-based access controls to subscriber functions
- Integrate with crisis communication platforms
- Primary Use Case:
-
Emerging/Innovative: Sustainability, Accessibility, and Underserved Segment Support
-
Key Business Outcomes:
- Enable routing or content adjustment for eco-conscious, accessibility-focused, or gig-economy workers
-
Expansion of service inclusivity
-
Relevant Customer Scenarios:
- Inquiries about sustainable products routed to specialists
- Preference flag triggers content in accessible formats (sign language, large text)
-
Gig workers sign up—get instant onboarding content based on selected preferences
-
Business Value Delivered:
- Foster new market segments (est. 10% growth in leads from new communities)
-
Enhance NPS among accessibility users by 25%
-
Recommended Next Steps:
- Expand preference model with sustainability and accessibility profile fields
- Partner with accessibility and gig-economy platform providers for new integrations