mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-08-03 13:44:09 +08:00
312 lines
17 KiB
Plaintext
312 lines
17 KiB
Plaintext
|
|
/**
|
|||
|
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
* DELETE TEST DATA - COMPREHENSIVE CLEANUP
|
|||
|
|
* Remove all test data matching common test data patterns
|
|||
|
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
*
|
|||
|
|
* PURPOSE:
|
|||
|
|
* Clean up test data created by TestDataFactory classes.
|
|||
|
|
* Targets common test data naming patterns across multiple objects.
|
|||
|
|
*
|
|||
|
|
* PATTERNS MATCHED:
|
|||
|
|
* • 'Test%' - Standard test prefix
|
|||
|
|
* • 'BulkTest%' - Bulk testing data
|
|||
|
|
* • 'BatchTest%' - Batch Apex testing data
|
|||
|
|
* • 'BulkData%' - Large volume test data
|
|||
|
|
* • 'BulkImport%' - Import testing data
|
|||
|
|
* • 'Hierarchy%' - Hierarchy testing data
|
|||
|
|
* • '%@testfactory.example.com' - Test email domain
|
|||
|
|
* • '%@bulktest.example.com' - Bulk test email domain
|
|||
|
|
*
|
|||
|
|
* ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// CONFIGURATION
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
Boolean previewOnly = true; // Set to false to actually delete
|
|||
|
|
Integer maxRecordsPerObject = 10000;
|
|||
|
|
|
|||
|
|
// Patterns to match (add your custom test patterns here)
|
|||
|
|
List<String> namePatterns = new List<String>{
|
|||
|
|
'Test%',
|
|||
|
|
'BulkTest%',
|
|||
|
|
'BatchTest%',
|
|||
|
|
'BulkData%',
|
|||
|
|
'BulkImport%',
|
|||
|
|
'Hierarchy%'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
List<String> emailPatterns = new List<String>{
|
|||
|
|
'%@testfactory.example.com',
|
|||
|
|
'%@bulktest.example.com',
|
|||
|
|
'%@batchtest.example.com'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// TRACKING
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
Map<String, Integer> recordCounts = new Map<String, Integer>();
|
|||
|
|
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════════════');
|
|||
|
|
System.debug('COMPREHENSIVE TEST DATA CLEANUP');
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════════════');
|
|||
|
|
System.debug('Mode: ' + (previewOnly ? 'PREVIEW ONLY' : '⚠️ DELETE MODE'));
|
|||
|
|
System.debug('Patterns: ' + namePatterns);
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════════════');
|
|||
|
|
System.debug('');
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// STEP 1: FIND TASKS (delete first - activity records)
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
List<Task> tasksToDelete = [
|
|||
|
|
SELECT Id, Subject, WhoId, WhatId
|
|||
|
|
FROM Task
|
|||
|
|
WHERE Subject LIKE 'Test%'
|
|||
|
|
OR Subject LIKE 'BulkTest%'
|
|||
|
|
OR Subject LIKE 'Hierarchy%'
|
|||
|
|
LIMIT :maxRecordsPerObject
|
|||
|
|
];
|
|||
|
|
recordCounts.put('Task', tasksToDelete.size());
|
|||
|
|
System.debug('📋 Tasks found: ' + tasksToDelete.size());
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// STEP 2: FIND EVENTS
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
List<Event> eventsToDelete = [
|
|||
|
|
SELECT Id, Subject, WhoId, WhatId
|
|||
|
|
FROM Event
|
|||
|
|
WHERE Subject LIKE 'Test%'
|
|||
|
|
OR Subject LIKE 'All Day Event%'
|
|||
|
|
OR Subject LIKE 'Recurring Event%'
|
|||
|
|
OR Subject LIKE 'Calendar Event%'
|
|||
|
|
LIMIT :maxRecordsPerObject
|
|||
|
|
];
|
|||
|
|
recordCounts.put('Event', eventsToDelete.size());
|
|||
|
|
System.debug('📋 Events found: ' + eventsToDelete.size());
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// STEP 3: FIND CASES
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
List<Case> casesToDelete = [
|
|||
|
|
SELECT Id, Subject, AccountId
|
|||
|
|
FROM Case
|
|||
|
|
WHERE Subject LIKE 'Test%'
|
|||
|
|
OR Subject LIKE 'Hierarchy Case%'
|
|||
|
|
LIMIT :maxRecordsPerObject
|
|||
|
|
];
|
|||
|
|
recordCounts.put('Case', casesToDelete.size());
|
|||
|
|
System.debug('📋 Cases found: ' + casesToDelete.size());
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// STEP 4: FIND OPPORTUNITIES
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
List<Opportunity> opportunitiesToDelete = [
|
|||
|
|
SELECT Id, Name, AccountId
|
|||
|
|
FROM Opportunity
|
|||
|
|
WHERE Name LIKE 'Test%'
|
|||
|
|
OR Name LIKE 'BulkTest%'
|
|||
|
|
OR Name LIKE 'BatchTest%'
|
|||
|
|
OR Name LIKE 'Hierarchy%'
|
|||
|
|
LIMIT :maxRecordsPerObject
|
|||
|
|
];
|
|||
|
|
recordCounts.put('Opportunity', opportunitiesToDelete.size());
|
|||
|
|
System.debug('📋 Opportunities found: ' + opportunitiesToDelete.size());
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// STEP 5: FIND CONTACTS
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
List<Contact> contactsToDelete = [
|
|||
|
|
SELECT Id, FirstName, LastName, Email, AccountId
|
|||
|
|
FROM Contact
|
|||
|
|
WHERE LastName LIKE 'Contact_%'
|
|||
|
|
OR LastName LIKE 'Hierarchy%'
|
|||
|
|
OR LastName LIKE 'Test%'
|
|||
|
|
OR Email LIKE '%@testfactory.example.com'
|
|||
|
|
OR Email LIKE '%@bulktest.example.com'
|
|||
|
|
OR Email LIKE '%@batchtest.example.com'
|
|||
|
|
LIMIT :maxRecordsPerObject
|
|||
|
|
];
|
|||
|
|
recordCounts.put('Contact', contactsToDelete.size());
|
|||
|
|
System.debug('📋 Contacts found: ' + contactsToDelete.size());
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// STEP 6: FIND LEADS
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
List<Lead> leadsToDelete = [
|
|||
|
|
SELECT Id, FirstName, LastName, Company
|
|||
|
|
FROM Lead
|
|||
|
|
WHERE LastName LIKE 'Lead%'
|
|||
|
|
OR LastName LIKE 'Test%'
|
|||
|
|
OR Company LIKE 'Test Company%'
|
|||
|
|
OR Email LIKE '%@testlead.example.com'
|
|||
|
|
LIMIT :maxRecordsPerObject
|
|||
|
|
];
|
|||
|
|
recordCounts.put('Lead', leadsToDelete.size());
|
|||
|
|
System.debug('📋 Leads found: ' + leadsToDelete.size());
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// STEP 7: FIND ACCOUNTS (delete last - parent records)
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
List<Account> accountsToDelete = [
|
|||
|
|
SELECT Id, Name
|
|||
|
|
FROM Account
|
|||
|
|
WHERE Name LIKE 'Test%'
|
|||
|
|
OR Name LIKE 'BulkTest%'
|
|||
|
|
OR Name LIKE 'BatchTest%'
|
|||
|
|
OR Name LIKE 'BulkData%'
|
|||
|
|
OR Name LIKE 'BulkImport%'
|
|||
|
|
OR Name LIKE 'Hierarchy%'
|
|||
|
|
OR Name LIKE 'Root Account%'
|
|||
|
|
OR Name LIKE 'Level%'
|
|||
|
|
LIMIT :maxRecordsPerObject
|
|||
|
|
];
|
|||
|
|
recordCounts.put('Account', accountsToDelete.size());
|
|||
|
|
System.debug('📋 Accounts found: ' + accountsToDelete.size());
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// SUMMARY
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
Integer totalRecords = 0;
|
|||
|
|
for (String objName : recordCounts.keySet()) {
|
|||
|
|
totalRecords += recordCounts.get(objName);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
System.debug('');
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════════════');
|
|||
|
|
System.debug('CLEANUP SUMMARY');
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════════════');
|
|||
|
|
System.debug(' Tasks: ' + recordCounts.get('Task'));
|
|||
|
|
System.debug(' Events: ' + recordCounts.get('Event'));
|
|||
|
|
System.debug(' Cases: ' + recordCounts.get('Case'));
|
|||
|
|
System.debug(' Opportunities: ' + recordCounts.get('Opportunity'));
|
|||
|
|
System.debug(' Contacts: ' + recordCounts.get('Contact'));
|
|||
|
|
System.debug(' Leads: ' + recordCounts.get('Lead'));
|
|||
|
|
System.debug(' Accounts: ' + recordCounts.get('Account'));
|
|||
|
|
System.debug(' ─────────────────────────────────');
|
|||
|
|
System.debug(' TOTAL: ' + totalRecords);
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════════════');
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// EXECUTE DELETE
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
if (!previewOnly && totalRecords > 0) {
|
|||
|
|
System.debug('');
|
|||
|
|
System.debug('⚠️ DELETING RECORDS...');
|
|||
|
|
System.debug(' Deleting in dependency order (children before parents)');
|
|||
|
|
System.debug('');
|
|||
|
|
|
|||
|
|
Integer deletedCount = 0;
|
|||
|
|
|
|||
|
|
// Delete in order: Activities → Children → Parents
|
|||
|
|
try {
|
|||
|
|
if (!tasksToDelete.isEmpty()) {
|
|||
|
|
delete tasksToDelete;
|
|||
|
|
deletedCount += tasksToDelete.size();
|
|||
|
|
System.debug(' ✓ Deleted ' + tasksToDelete.size() + ' Tasks');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!eventsToDelete.isEmpty()) {
|
|||
|
|
delete eventsToDelete;
|
|||
|
|
deletedCount += eventsToDelete.size();
|
|||
|
|
System.debug(' ✓ Deleted ' + eventsToDelete.size() + ' Events');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!casesToDelete.isEmpty()) {
|
|||
|
|
delete casesToDelete;
|
|||
|
|
deletedCount += casesToDelete.size();
|
|||
|
|
System.debug(' ✓ Deleted ' + casesToDelete.size() + ' Cases');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!opportunitiesToDelete.isEmpty()) {
|
|||
|
|
delete opportunitiesToDelete;
|
|||
|
|
deletedCount += opportunitiesToDelete.size();
|
|||
|
|
System.debug(' ✓ Deleted ' + opportunitiesToDelete.size() + ' Opportunities');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!contactsToDelete.isEmpty()) {
|
|||
|
|
delete contactsToDelete;
|
|||
|
|
deletedCount += contactsToDelete.size();
|
|||
|
|
System.debug(' ✓ Deleted ' + contactsToDelete.size() + ' Contacts');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!leadsToDelete.isEmpty()) {
|
|||
|
|
delete leadsToDelete;
|
|||
|
|
deletedCount += leadsToDelete.size();
|
|||
|
|
System.debug(' ✓ Deleted ' + leadsToDelete.size() + ' Leads');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!accountsToDelete.isEmpty()) {
|
|||
|
|
delete accountsToDelete;
|
|||
|
|
deletedCount += accountsToDelete.size();
|
|||
|
|
System.debug(' ✓ Deleted ' + accountsToDelete.size() + ' Accounts');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
System.debug('');
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════════════');
|
|||
|
|
System.debug('✅ CLEANUP COMPLETE');
|
|||
|
|
System.debug(' Deleted: ' + deletedCount + ' records');
|
|||
|
|
System.debug(' Status: Moved to Recycle Bin');
|
|||
|
|
System.debug('═══════════════════════════════════════════════════════════════');
|
|||
|
|
|
|||
|
|
} catch (DmlException e) {
|
|||
|
|
System.debug('');
|
|||
|
|
System.debug('❌ DELETE ERROR: ' + e.getMessage());
|
|||
|
|
System.debug(' Some records may have dependencies preventing deletion.');
|
|||
|
|
System.debug(' Try running again after resolving dependencies.');
|
|||
|
|
}
|
|||
|
|
} else if (previewOnly) {
|
|||
|
|
System.debug('');
|
|||
|
|
System.debug('ℹ️ PREVIEW MODE - No records deleted');
|
|||
|
|
System.debug(' To delete, set previewOnly = false and run again');
|
|||
|
|
System.debug('');
|
|||
|
|
System.debug(' ⚠️ Please review the counts above before deleting!');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
// BATCH APEX VERSION (For very large datasets)
|
|||
|
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|||
|
|
|
|||
|
|
/*
|
|||
|
|
For datasets larger than 10,000 records, use batch Apex:
|
|||
|
|
|
|||
|
|
public class TestDataCleanupBatch implements Database.Batchable<SObject> {
|
|||
|
|
private String objectName;
|
|||
|
|
private String query;
|
|||
|
|
|
|||
|
|
public TestDataCleanupBatch(String objName, String soqlQuery) {
|
|||
|
|
this.objectName = objName;
|
|||
|
|
this.query = soqlQuery;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public Database.QueryLocator start(Database.BatchableContext bc) {
|
|||
|
|
return Database.getQueryLocator(query);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void execute(Database.BatchableContext bc, List<SObject> records) {
|
|||
|
|
delete records;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void finish(Database.BatchableContext bc) {
|
|||
|
|
System.debug('Cleanup complete for ' + objectName);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Execute for each object (in order):
|
|||
|
|
Database.executeBatch(new TestDataCleanupBatch('Task',
|
|||
|
|
'SELECT Id FROM Task WHERE Subject LIKE \'Test%\''), 200);
|
|||
|
|
Database.executeBatch(new TestDataCleanupBatch('Account',
|
|||
|
|
'SELECT Id FROM Account WHERE Name LIKE \'Test%\''), 200);
|
|||
|
|
*/
|